From d5fe2181b1c7e22916a987ce74f955b78559df78 Mon Sep 17 00:00:00 2001 From: FancMa01 Date: Wed, 31 Jan 2024 10:15:09 -0700 Subject: [PATCH] Mfancher/orbit (#684) * saving initial structure * Initial plugins screen to activate orbit Plugins screen introduced to give users the ability to add Orbit functionality to their installation. Still need to add functionality and security checks to ensure only LNRS users can see and activate * initial commit * implement orbit builds data type * Route functionality and verification added * Add Orbits dashboard page, move charts and css into common from notifications Add Orbits dashboard page. Move charts and css into common from notifications to make them shared to be editable and changeable in one spot rather than two. * add charts to orbit builds add charts, need to adjust pulling query to pull everything, not just megaphone * update lots of stuff, plugins changed * updates, adjusting plugin screens * fully functional updating notification methods * fetch and notify job and emails working need to adjust teams messages, getting error * update filters on dashboard to include orbit * change connection to mssql with env variables * fix teams notifications * updates * saving progress before testing extension * this is a test commit * This is a test commit to remove the last commit I made * This is a proof of concept for Dan, commit message 2.0 * updates so monitorings save properly * saving progress * saving progress dahsboard tables working * save progress * orbit dashboard finished * Saving progress before pushing up Most items should be functional, adding polish and working on notifications. Still need to finish notifications, "hiding" the plugin for non LNRS users, and only showing screen navigation/allowing screens to be accessed if plugin is active. * progress saving * add product and business unit fields * add host field * add primary and secondary contact add primary and secondary contact * update orbitdatafetch to use dimreceiveinstance table since that is where megaphone appears now * emails notifications udpated and working * Rename Plugins to Integrations on the front-end * cleanup and teams webhooks working * saving progress * rework monitoring + dashboard to include initial and final build status * reworked filters and generated filter content from items * Fixed filtering to be applying to build level now as well Have bug that I need to fix where filtered work unit list is not matching the count column. I believe the count column is correct and the filtered WU list is wrong as it has displayed odd behavior in the past * fixed refresh bug by only allowed certain parameters to be saved in url * update filters to reduce lines of code * counters set * complete refactor * removed filtervalues to simplify even more * Remove initial dashboardFilter set to make code even easier/cleaner * Fetch domains and products from fido * Fix two small errors from merge * adjust orbit jobs to new job scheduler * adjustment in the route to call new job name * codeql fix * codeql fix attempt 2 * add sqlstring to escape dangerous user input * fix another user input * another dangerous input * codeql........ * save * saving more progress * tests finished and gitignore adjusted * Bug fixes * more test and bug fixes * integration creation only running once on server start * loading errors fixed if values are returned or backend returning errors * fix sql error on search close all sql connections after use so they are not re-used * Remove Timers and fix eternal spinning with no data --------- Co-authored-by: Matt Fancher --- client-reactjs/src/App.js | 8 + .../src/components/admin/Integrations.js | 203 ++ .../application/dashboard/Orbit/Filters.jsx | 336 +++ .../application/dashboard/Orbit/Orbit.jsx | 497 ++++ .../dashboard/Orbit/OrbitTable.jsx | 77 + .../charts/Donut.jsx | 0 .../dashboard/common/charts/MetricBoxes.jsx | 25 + .../charts/NotificationCharts.jsx | 0 .../{notifications => common}/charts/Pie.jsx | 0 .../charts/StackedBar.jsx | 0 .../common/charts/WorkUnitCharts.jsx | 35 + .../{notifications => common/css}/index.css | 0 .../monitoringStatusOptions.js | 0 .../dashboard/notifications/BulkActions.jsx | 2 +- .../notifications/{charts => }/Filters.jsx | 49 +- .../notifications/charts/MetricBoxes.jsx | 20 - .../dashboard/notifications/index.js | 10 +- .../application/orbitMonitoring/BasicTab.jsx | 235 ++ .../orbitMonitoring/MonitoringTab.jsx | 312 +++ .../orbitMonitoring/NotificationsTab.jsx | 162 ++ .../orbitMonitoring/OrbitMonitoring.jsx | 120 + .../orbitMonitoring/OrbitMonitoringModal.jsx | 351 +++ .../orbitMonitoring/OrbitMonitoringTable.jsx | 120 + .../src/components/common/Constants.js | 2 + .../src/components/layout/Header.js | 1 + .../src/components/layout/LeftNav.js | 25 +- client-reactjs/src/index.css | 34 + .../src/redux/actions/Application.js | 15 + .../src/redux/reducers/ApplicationReducer.js | 11 + eclcc.log | 13 + server/.gitignore | 5 + server/job-scheduler.js | 29 + server/jobSchedularMethods/clusterJobs.js | 2 +- server/jobSchedularMethods/hpccFiles.js | 44 - server/jobSchedularMethods/orbitJobs.js | 58 + server/jobs/integrationCreation.js | 60 + .../jobs/messageCards/notificationTemplate.js | 315 ++- server/jobs/orbitMegaphone.js | 187 ++ server/jobs/submitOrbitMonitoring.js | 363 +++ ...taData_to_monitoring_notification_table.js | 2 +- .../20230906000000-create-OrbitMonitoring.js | 89 + .../20230906195957-create-orbit-builds.js | 66 + .../20230907145012-create-integrations.js | 43 + server/models/integrations.js | 53 + server/models/orbitMonitoring.js | 76 + server/models/orbitbuilds.js | 60 + server/package-lock.json | 2072 ++++++++++++++++- server/package.json | 4 +- server/routes/integrations/read.js | 108 + server/routes/notifications/read.js | 12 +- server/routes/orbit/read.js | 840 +++++++ server/server.js | 6 +- server/tempFiles/Tombolo-Notifications.CSV | 1 - server/tempFiles/Tombolo-Notifications.JSON | 1 - server/tempFiles/Tombolo-clusterUsage.CSV | 34 - server/tempFiles/Tombolo-clusterUsage.JSON | 1 - .../tests/mock-data/OrbitMonitoringBad.json | 32 + server/tests/mock-data/cluster.json | 1 + server/tests/mock-data/global.json | 8 + server/tests/mock-data/integration.json | 4 + server/tests/mock-data/orbitMonitoring.json | 32 + server/tests/mock-data/sqlData.json | 16 + server/tests/unit/integration.test.js | 86 + server/tests/unit/notification.test.js | 14 +- server/tests/unit/orbit.test.js | 279 +++ 65 files changed, 7484 insertions(+), 182 deletions(-) create mode 100644 client-reactjs/src/components/admin/Integrations.js create mode 100644 client-reactjs/src/components/application/dashboard/Orbit/Filters.jsx create mode 100644 client-reactjs/src/components/application/dashboard/Orbit/Orbit.jsx create mode 100644 client-reactjs/src/components/application/dashboard/Orbit/OrbitTable.jsx rename client-reactjs/src/components/application/dashboard/{notifications => common}/charts/Donut.jsx (100%) create mode 100644 client-reactjs/src/components/application/dashboard/common/charts/MetricBoxes.jsx rename client-reactjs/src/components/application/dashboard/{notifications => common}/charts/NotificationCharts.jsx (100%) rename client-reactjs/src/components/application/dashboard/{notifications => common}/charts/Pie.jsx (100%) rename client-reactjs/src/components/application/dashboard/{notifications => common}/charts/StackedBar.jsx (100%) create mode 100644 client-reactjs/src/components/application/dashboard/common/charts/WorkUnitCharts.jsx rename client-reactjs/src/components/application/dashboard/{notifications => common/css}/index.css (100%) rename client-reactjs/src/components/application/dashboard/{notifications => common}/monitoringStatusOptions.js (100%) rename client-reactjs/src/components/application/dashboard/notifications/{charts => }/Filters.jsx (84%) delete mode 100644 client-reactjs/src/components/application/dashboard/notifications/charts/MetricBoxes.jsx create mode 100644 client-reactjs/src/components/application/orbitMonitoring/BasicTab.jsx create mode 100644 client-reactjs/src/components/application/orbitMonitoring/MonitoringTab.jsx create mode 100644 client-reactjs/src/components/application/orbitMonitoring/NotificationsTab.jsx create mode 100644 client-reactjs/src/components/application/orbitMonitoring/OrbitMonitoring.jsx create mode 100644 client-reactjs/src/components/application/orbitMonitoring/OrbitMonitoringModal.jsx create mode 100644 client-reactjs/src/components/application/orbitMonitoring/OrbitMonitoringTable.jsx create mode 100644 eclcc.log create mode 100644 server/jobSchedularMethods/orbitJobs.js create mode 100644 server/jobs/integrationCreation.js create mode 100644 server/jobs/orbitMegaphone.js create mode 100644 server/jobs/submitOrbitMonitoring.js create mode 100644 server/migrations/20230906000000-create-OrbitMonitoring.js create mode 100644 server/migrations/20230906195957-create-orbit-builds.js create mode 100644 server/migrations/20230907145012-create-integrations.js create mode 100644 server/models/integrations.js create mode 100644 server/models/orbitMonitoring.js create mode 100644 server/models/orbitbuilds.js create mode 100644 server/routes/integrations/read.js create mode 100644 server/routes/orbit/read.js delete mode 100644 server/tempFiles/Tombolo-Notifications.CSV delete mode 100644 server/tempFiles/Tombolo-Notifications.JSON delete mode 100644 server/tempFiles/Tombolo-clusterUsage.CSV delete mode 100644 server/tempFiles/Tombolo-clusterUsage.JSON create mode 100644 server/tests/mock-data/OrbitMonitoringBad.json create mode 100644 server/tests/mock-data/global.json create mode 100644 server/tests/mock-data/integration.json create mode 100644 server/tests/mock-data/orbitMonitoring.json create mode 100644 server/tests/mock-data/sqlData.json create mode 100644 server/tests/unit/integration.test.js create mode 100644 server/tests/unit/orbit.test.js diff --git a/client-reactjs/src/App.js b/client-reactjs/src/App.js index ee7ec971b..86c27c402 100644 --- a/client-reactjs/src/App.js +++ b/client-reactjs/src/App.js @@ -35,9 +35,13 @@ const ManualJobDetail = React.lazy(() => import('./components/application/Jobs/M const Actions = React.lazy(() => import('./components/application/actions/actions')); const AddJobsForm = React.lazy(() => import('./components/application/Jobs/AddjobsForm/AddJobsForm')); const FileMonitoring = React.lazy(() => import('./components/application/fileMonitoring/FileMonitoring')); +const OrbitMonitoring = React.lazy(() => import('./components/application/orbitMonitoring/OrbitMonitoring')); const SuperFileMonitoring = React.lazy(() => import('./components/application/superfileMonitoring/SuperFileMonitoring') ); +// const Notifications = React.lazy(() => import('./components/application/dashboard/notifications/Notifications')); +const Orbit = React.lazy(() => import('./components/application/dashboard/Orbit/Orbit')); +// const ClusterUsage = React.lazy(() => import('./components/application/dashboard/clusterUsage/ClusterUsage')); const Notifications = React.lazy(() => import('./components/application/dashboard/notifications')); const ClusterUsage = React.lazy(() => import('./components/application/dashboard/clusterUsage/')); const ClusterMonitoring = React.lazy(() => import('./components/application/clusterMonitoring')); @@ -53,6 +57,7 @@ const Regulations = React.lazy(() => import('./components/admin/ControlsAndRegul const GitHubSettings = React.lazy(() => import('./components/admin/GitHubSettings/GitHubSettings')); const ScheduledJobsPage = React.lazy(() => import('./components/admin/ScheduledJobsPage')); const Compliance = React.lazy(() => import('./components/admin/Compliance/Compliance')); +const Integrations = React.lazy(() => import('./components/admin/Integrations')); const TeamsNotification = React.lazy(() => import('./components/admin/notifications/MsTeams/Teams')); // Shared layout, etc. @@ -179,9 +184,11 @@ class App extends React.Component { + {' '} + @@ -206,6 +213,7 @@ class App extends React.Component { + { + const [integrations, setIntegrations] = useState([]); + const [modalVisible, setModalVisible] = useState(false); + const [modalWidth, setModalWidth] = useState(0); + const [confirmLoading, setConfirmLoading] = useState(false); + const [selectedIntegration, setSelectedIntegration] = useState({}); + const [notifications, setNotifications] = useState({}); + const [active, setActive] = useState(false); + const [notificationForm] = Form.useForm(); + const windowSize = useWindowSize(); + + // Changes modal size per screen vw + useEffect(() => { + const { width } = windowSize.inner; + if (width > 1500) { + setModalWidth('40vw'); + } else if (width > 1000) { + setModalWidth('60vw'); + } else { + setModalWidth('100vw'); + } + }, [windowSize]); + + const dispatch = useDispatch(); + const { + application: { applicationId }, + } = useSelector((state) => state.applicationReducer); + + useEffect(() => { + if (applicationId) getIntegrations(); + }, [applicationId]); + + const getIntegrations = async () => { + try { + const payload = { + method: 'GET', + header: authHeader(), + }; + const response = await fetch(`/api/integrations/get/${applicationId}`, payload); + + const data = await response.json(); + if (data) { + setIntegrations(data); + } + dispatch(applicationActions.getIntegrations(applicationId)); + } catch (err) { + console.log(err); + } + }; + + const editIntegration = async (record) => { + await setModalVisible(true); + await setSelectedIntegration(record); + await setNotifications(record.metaData); + }; + + const handleSave = async () => { + setConfirmLoading(true); + + const body = { notifications, active: { megaphoneActive: active } }; + + const payload = { + method: 'PUT', + header: authHeader(), + body: JSON.stringify(body), + }; + + const response = await fetch(`/api/integrations/update/${applicationId}/${selectedIntegration.name}`, payload); + + if (response.ok) { + getIntegrations(); + setConfirmLoading(false); + setModalVisible(false); + dispatch(applicationActions.getIntegrations(applicationId)); + + notificationForm.resetFields(); + message.success('Successfully updated Integration'); + } else { + message.success('An Error Occured, Integration not updated'); + } + }; + + const saveBtn = ( + + ); + const cancelModal = async () => { + setModalVisible(false); + }; + + const toggleIntegration = async (name) => { + try { + const payload = { + method: 'PUT', + header: authHeader(), + }; + const response = await fetch(`/api/integrations/toggle/${applicationId}/${name}`, payload); + + if (response.ok) { + getIntegrations(); + } + dispatch(applicationActions.getIntegrations(applicationId)); + } catch (err) { + console.log(err); + } + }; + + const columns = [ + { title: 'Name', dataIndex: 'name' }, + { title: 'Description', dataIndex: 'description' }, + { + title: 'Activate', + dataIndex: 'activate', + render: (_, record) => ( + + + + toggleIntegration(record.name)} /> + + + + ), + }, + { + title: 'Edit', + dataIndex: 'edit', + render: (_, record) => ( + + + editIntegration(record)} /> + + + ), + }, + ]; + + return ( + <> + +
+ record.id} /> + +
+

Megaphone Notification Settings

+ + { + setActive(e); + }}> + + + setNotifications({ ...notifications, notificationEmails: e.target.value })}> + + + setNotifications({ ...notifications, notificationWebhooks: e.target.value })}> + + + setNotifications({ ...notifications, notificationEmailsSev3: e.target.value })}> + + +
+ + ); +}; + +export default Integrations; diff --git a/client-reactjs/src/components/application/dashboard/Orbit/Filters.jsx b/client-reactjs/src/components/application/dashboard/Orbit/Filters.jsx new file mode 100644 index 000000000..274d2854d --- /dev/null +++ b/client-reactjs/src/components/application/dashboard/Orbit/Filters.jsx @@ -0,0 +1,336 @@ +import React, { useState } from 'react'; +import { Form, Select, DatePicker, Button, Checkbox, Drawer } from 'antd'; +import { useHistory } from 'react-router-dom'; +import { handleError } from '../../../common/AuthHeader.js'; +import '../common/css/index.css'; +import { FilterOutlined } from '@ant-design/icons'; + +// Form layout +const layout = { + labelCol: { span: 24 }, + wrapperCol: { span: 24 }, +}; +function Filters({ groupDataBy, setGroupDataBy, dashboardFilters, setDashboardFilters }) { + const [form] = Form.useForm(); + const history = useHistory(); + const [open, setOpen] = useState(false); + + const showDrawer = () => { + setOpen(true); + }; + + const onClose = () => { + setOpen(false); + }; + + // When form is submitted + const onFinish = () => { + try { + form.validateFields(); + } catch (err) { + handleError(err); + } + }; + + // Disable future dates + const disabledDate = (current) => { + const date = new Date(); + const todaysDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1); + return current && current >= todaysDate; + }; + + // When filters are changed - add to url params to persist on page refresh + const updateParams = (param) => { + const newParams = new URLSearchParams(); + const allFilters = { ...dashboardFilters, ...param }; + const allowedURLParams = ['initialStatus', 'finalStatus', 'severity', 'dateRange', 'groupDataBy']; + for (let key in allFilters) { + //only allow certain params to be added to url + if (allowedURLParams.includes(key)) { + newParams.set(key, allFilters[key]); + } + } + history.push(`?${newParams.toString()}`); + }; + + //function to select or clear all when select all boxes are checked + const selectAll = (e, option) => { + //concatenate so we can dyanmically call the correct state variable to avoid case/switch + const optionConcat = option + 'Options'; + + if (e.target.checked) { + form.setFieldsValue({ [option]: dashboardFilters[optionConcat].map((option) => option.value) }); + setDashboardFilters((prev) => ({ + ...prev, + [option]: dashboardFilters[optionConcat].map((option) => option.value), + })); + updateParams({ [option]: dashboardFilters[optionConcat].map((option) => option.value) }); + } else { + form.setFieldsValue({ [option]: [] }); + setDashboardFilters((prev) => ({ ...prev, [option]: [] })); + updateParams({ [option]: [] }); + } + }; + + return ( + <> + + +
+
+

Workunit Filters

+ + { + setDashboardFilters((prev) => ({ ...prev, finalStatus: values })); + updateParams({ finalStatus: values }); + }} + dropdownRender={(menu) => ( + <> +
+ selectAll(e, 'finalStatus')}> + Select All + + + {menu} +
+ + )} + /> +
+ + + + + + + + + +

Build Filters

+ + + + + + + + + + + + + +

Chart Slicers

+ + + { + setDashboardFilters((prev) => ({ ...prev, dateRange: value })); + updateParams({ dateRange: value }); + const numberOfDays = Math.ceil(Math.abs(value[0] - value[1]) / (1000 * 60 * 60 * 24) + 1); + let suggestedFilterByOption; + if (numberOfDays <= 15) { + setGroupDataBy('day'); + suggestedFilterByOption = 'day'; + } else if (numberOfDays > 15 && numberOfDays <= 105) { + setGroupDataBy('week'); + suggestedFilterByOption = 'week'; + } else if (numberOfDays > 105 && numberOfDays <= 548) { + setGroupDataBy('month'); + suggestedFilterByOption = 'month'; + } else { + suggestedFilterByOption = 'year'; + setGroupDataBy('year'); + } + form.setFieldsValue({ groupDataBy: suggestedFilterByOption }); + }} + /> + + + + + + + + + + +
+
+ + ); +} +export default Filters; diff --git a/client-reactjs/src/components/application/dashboard/Orbit/Orbit.jsx b/client-reactjs/src/components/application/dashboard/Orbit/Orbit.jsx new file mode 100644 index 000000000..df8a70c96 --- /dev/null +++ b/client-reactjs/src/components/application/dashboard/Orbit/Orbit.jsx @@ -0,0 +1,497 @@ +import React, { useEffect, useState } from 'react'; +import { Tabs, Empty, Spin, Space } from 'antd'; +import { useSelector } from 'react-redux'; +import moment from 'moment'; +import OrbitTable from './OrbitTable'; +import WorkUnitCharts from '../common/charts/WorkUnitCharts'; +import Filters from './Filters'; +import MetricBoxes from '../common/charts/MetricBoxes'; +import '../common/css/index.css'; +import ExportMenu from '../ExportMenu/ExportMenu'; +import { authHeader, handleError } from '../../../common/AuthHeader.js'; +import { message } from 'antd'; + +function Orbit() { + //all states needed to manage data + const [builds, setBuilds] = useState([]); + const [workUnits, setWorkUnits] = useState([]); + const [filteredWorkUnits, setFilteredWorkUnits] = useState(null); + const [filteredBuilds, setFilteredBuilds] = useState(null); + + //states for charts and titles + const [titleMetrics, setTitleMetrics] = useState([]); + const [metrics, setMetrics] = useState([]); + const [stackBarData, setStackBarData] = useState([]); + const [donutData, setDonutData] = useState([]); + const [groupDataBy, setGroupDataBy] = useState('day'); + + //states for filters -- place values initially to avoid load errors + const [dashboardFilters, setDashboardFilters] = useState({ + initialStatus: [], + initialStatusOptions: [], + finalStatus: [], + finalStatusOptions: [], + version: [], + versionOptions: [], + severity: [], + severityOptions: [], + builds: [], + buildsOptions: [], + products: [], + productsOptions: [], + businessUnits: [], + businessUnitsOptions: [], + wuid: [], + wuidOptions: [], + dateRange: [moment().subtract(15, 'days'), moment()], + groupDataBy: 'day', + }); + + //states for spinners + const [loading, setLoading] = useState(false); + + //grab the application ID from redux + const { + application: { applicationId }, + } = useSelector((item) => item.applicationReducer); + + // Step 1 - first we are loading the initial builds and workunits into state + useEffect(() => { + getbuilds(); + }, [applicationId]); + + //Get list of all builds, workunits, and set initial values and filters + const getbuilds = async () => { + try { + setLoading(true); + const payload = { + method: 'GET', + header: authHeader(), + }; + + if (applicationId === undefined) return; + + const response = await fetch(`/api/orbit/allMonitoring/${applicationId}`, payload); + if (!response.ok) { + setLoading(false); + handleError(response); + } + const data = await response.json(); + + //get work unit information and put it in builds information + const response2 = await fetch(`/api/orbit/getWorkUnits/${applicationId}`, payload); + if (!response2.ok) { + setLoading(false); + handleError(response2); + } + const data2 = await response2.json(); + + let builds2 = []; + + await Promise.all( + //add data2 workunits to matching builds + (builds2 = data.map((build) => { + const wu = data2.filter((workUnit) => workUnit.name === build.build); + return { ...build, workUnits: wu }; + })) + ); + + //combine and set work units + let totalWuList = data2; + + totalWuList.sort((a, b) => { + return new Date(b.metaData.lastRun) - new Date(a.metaData.lastRun); + }); + + //move stuff out of metaData to top level to make it easier to work with + totalWuList.forEach((workUnit) => { + workUnit.key = workUnit.id; + workUnit.initialStatus = workUnit.metaData.initialStatus; + workUnit.finalStatus = workUnit.metaData.finalStatus; + workUnit.version = workUnit.metaData.version; + workUnit.status = workUnit.metaData.status; + workUnit.lastRun = workUnit.metaData.lastRun; + }); + + //get unique values from workunit list + const uniqueInitialStatus = [...new Set(totalWuList.map((item) => item.initialStatus.toUpperCase()))]; + const uniqueFinalStatus = [...new Set(totalWuList.map((item) => item.finalStatus.toUpperCase()))]; + const uniqueVersion = [...new Set(totalWuList.map((item) => item.version))]; + const uniqueWorkUnits = [...new Set(totalWuList.map((item) => item.wuid))]; + + //get unique values from builds list + const uniqueSeverity = [...new Set(builds2.map((item) => item.severityCode))]; + const uniqueBuilds = [...new Set(builds2.map((item) => item.build))]; + const uniqueProducts = [...new Set(builds2.map((item) => item.product))]; + const uniqueBusinessUnits = [...new Set(builds2.map((item) => item.businessUnit))]; + + //create options for dropdowns + const uniqueInitialStatusOptions = []; + uniqueInitialStatus.forEach((item) => uniqueInitialStatusOptions.push({ label: item, value: item })); + const uniqueFinalStatusOptions = []; + uniqueFinalStatus.forEach((item) => uniqueFinalStatusOptions.push({ label: item, value: item })); + const uniqueVersionOptions = []; + uniqueVersion.forEach((item) => uniqueVersionOptions.push({ label: item, value: item })); + const uniqueSeverityOptions = []; + uniqueSeverity.forEach((item) => uniqueSeverityOptions.push({ label: item, value: item })); + const uniqueBuildsOptions = []; + uniqueBuilds.forEach((item) => uniqueBuildsOptions.push({ label: item, value: item })); + const uniqueProductsOptions = []; + uniqueProducts.forEach((item) => uniqueProductsOptions.push({ label: item, value: item })); + const uniqueBusinessUnitsOptions = []; + uniqueBusinessUnits.forEach((item) => uniqueBusinessUnitsOptions.push({ label: item, value: item })); + const uniqueWorkUnitsOptions = []; + uniqueWorkUnits.forEach((item) => uniqueWorkUnitsOptions.push({ label: item, value: item })); + + //check URL params for filters + const params = new URLSearchParams(location.search); + const filters = {}; + if (params.get('initialStatus')) { + filters.initialStatus = params.get('initialStatus')?.split(','); + } + if (params.get('finalStatus')) { + filters.finalStatus = params.get('finalStatus')?.split(','); + } + if (params.get('dateRange')) { + const dateString = params.get('dateRange'); + const dates = dateString.split(','); + const range = [moment(dates[0]), moment(dates[1])]; + filters.dateRange = range; + } + if (params.get('groupDataBy')) { + filters.groupDataBy = params.get('groupDataBy'); + setGroupDataBy(params.get('groupDataBy')); + // setGroupDataBy('day'); + } + + setWorkUnits(totalWuList); + + setBuilds(builds2); + + //set them in state for the initial load + setDashboardFilters((dashboardFilters) => ({ + ...dashboardFilters, + initialStatus: filters.initialStatus?.length ? filters.initialStatus : uniqueInitialStatus, + initialStatusOptions: uniqueInitialStatusOptions, + finalStatus: filters.finalStatus?.length ? filters.finalStatus : uniqueFinalStatus, + finalStatusOptions: uniqueFinalStatusOptions, + version: uniqueVersion, + versionOptions: uniqueVersionOptions, + severity: uniqueSeverity, + severityOptions: uniqueSeverityOptions, + builds: uniqueBuilds, + buildsOptions: uniqueBuildsOptions, + products: uniqueProducts, + productsOptions: uniqueProductsOptions, + businessUnits: uniqueBusinessUnits, + businessUnitsOptions: uniqueBusinessUnitsOptions, + wuid: uniqueWorkUnits, + wuidOptions: uniqueWorkUnitsOptions, + dateRange: filters.dateRange?.length ? filters.dateRange : [moment().subtract(15, 'days'), moment()], + groupDataBy: filters.groupDataBy ? filters.groupDataBy : 'day', + })); + + setLoading(false); + } catch (error) { + message.error('Failed to fetch builds' + error); + console.log(error); + } + }; + + // Step 2 - now we need to apply the initial filters to the initial data + useEffect(() => { + //if filtered builds and workunits are empty and the initial data and filters are set, filter data + if ( + filteredBuilds === null && + filteredWorkUnits === null && + builds.length > 0 && + workUnits.length > 0 && + dashboardFilters?.initialStatus?.length > 0 + ) { + filterData(); + } + }, [builds, workUnits, dashboardFilters]); + + const filterData = () => { + let filteredBuildsList = []; + + if (builds.length === 0 || workUnits.length === 0) return; + //apply filters to build list + builds.forEach((build) => { + if ( + dashboardFilters.severity.includes(build.severityCode) && + dashboardFilters.builds.includes(build.build) && + dashboardFilters.products.includes(build.product) && + dashboardFilters.businessUnits.includes(build.businessUnit) + ) { + filteredBuildsList.push(build); + } else { + return; + } + }); + + setFilteredBuilds(filteredBuildsList); + + //get a list of just the names to filter work units + let filteredBuildNameList = filteredBuildsList.map((build) => build.build); + + //apply filters to work units once the build list is filtered + let filtered = workUnits.filter((workUnit) => { + let wuDate = moment(workUnit.lastRun); + + if ( + wuDate > moment(dashboardFilters.dateRange[0]) && + wuDate < moment(dashboardFilters.dateRange[1]) && + dashboardFilters.initialStatus?.includes(workUnit.initialStatus.toUpperCase()) && + dashboardFilters.finalStatus?.includes(workUnit.finalStatus.toUpperCase()) && + dashboardFilters.version?.includes(workUnit.version) && + filteredBuildNameList.includes(workUnit.name) + ) { + return true; + } else { + return false; + } + }); + + setFilteredWorkUnits(filtered); + }; + + //Step 3 - now that the filtered builds and workunits are set, we can get the counts and chart data + useEffect(() => { + if (filteredBuilds !== null && filteredWorkUnits !== null && filteredBuilds[0].count === undefined) { + getCounts(); + setChartData(); + } + }, [filteredBuilds, filteredWorkUnits]); + + const getCounts = () => { + let builds2 = []; + let wus = []; + + filteredBuilds.forEach((build) => { + let count = 0; + + if (build.workUnits?.length > 0) { + build.workUnits.forEach((workUnit) => { + let wuDate = moment(workUnit.lastRun); + + if ( + wuDate > moment(dashboardFilters.dateRange[0]) && + wuDate < moment(dashboardFilters?.dateRange[1]) && + dashboardFilters.initialStatus?.includes(workUnit.initialStatus.toUpperCase()) && + dashboardFilters.finalStatus?.includes(workUnit.finalStatus.toUpperCase()) && + dashboardFilters.version?.includes(workUnit.version) && + dashboardFilters.builds?.includes(workUnit.name) + ) { + //check if workunit already exists in wus list to avoid duplicates + let wuExists = wus.find((item) => item.id === workUnit.id); + if (!wuExists) { + wus.push(workUnit); + count++; + } + } + }); + } + + builds2.push({ ...build, count: count }); + }); + + setFilteredBuilds(builds2); + setLoading(false); + }; + + const setChartData = () => { + if (filteredWorkUnits) { + const newMetrics = []; // Pie data + const newStackBarData = []; // Stack bar Data + const newDonutData = []; // Donut data + const newTitleMetrics = []; //title metrics + + newTitleMetrics.push({ title: 'Work Units', description: filteredWorkUnits.length }); + newTitleMetrics.push({ + title: 'Builds', + description: filteredBuilds.length > 0 ? filteredBuilds.length : builds.length, + }); + //get date range from filters + if (dashboardFilters?.dateRange) { + newTitleMetrics.push({ + title: 'Date Range Selected', + description: + moment(dashboardFilters?.dateRange[0]).format('MM/DD/YY') + + ' - ' + + moment(dashboardFilters?.dateRange[1]).format('MM/DD/YY'), + }); + } + + const workUnitCountByInitialStatus = {}; + const workUnitCountByFinalStatus = {}; + const workUnitCountByBuild = {}; + + let data; + switch (groupDataBy) { + case 'week': + data = filteredWorkUnits.map((workUnit) => { + const weekStart = moment(workUnit.lastRun).startOf('week').format('MM/DD/YY'); + const weekEnd = moment(workUnit.lastRun).endOf('week').format('MM/DD/YY'); + const updatedItem = { ...workUnit }; + updatedItem.metaData.lastRun = `${weekStart} - ${weekEnd}`; + return updatedItem; + }); + break; + + case 'month': + data = filteredWorkUnits.map((workUnit) => { + const updatedItem = { ...workUnit }; + updatedItem.metaData.lastRun = moment(moment(workUnit.lastRun).utc(), 'MM/DD/YYYY').format('MMMM YYYY'); + return updatedItem; + }); + break; + + case 'quarter': + data = filteredWorkUnits.map((workUnit) => { + const updatedItem = { ...workUnit }; + const date = moment.utc(workUnit.lastRun); + const year = date.year(); + const quarter = Math.ceil((date.month() + 1) / 3); + updatedItem.metaData.lastRun = `${year} - Q${quarter}`; + return updatedItem; + }); + break; + + case 'year': + data = filteredWorkUnits.map((workUnit) => { + const updatedItem = { ...workUnit }; + const date = moment.utc(workUnit.lastRun); + const year = date.year(); + updatedItem.metaData.lastRun = year; + return updatedItem; + }); + break; + + default: + data = filteredWorkUnits; + } + //--------------------------------------- + data.forEach((workUnit) => { + if (workUnitCountByInitialStatus[workUnit?.initialStatus]) { + const newCountInitial = workUnitCountByInitialStatus[workUnit.initialStatus] + 1; + workUnitCountByInitialStatus[workUnit.initialStatus] = newCountInitial; + } else { + workUnitCountByInitialStatus[workUnit?.initialStatus] = 1; + } + + if (workUnitCountByFinalStatus[workUnit?.finalStatus]) { + const newCountFinal = workUnitCountByFinalStatus[workUnit.finalStatus] + 1; + workUnitCountByFinalStatus[workUnit.finalStatus] = newCountFinal; + } else { + workUnitCountByFinalStatus[workUnit?.finalStatus] = 1; + } + + if (groupDataBy == 'day') { + newStackBarData.push({ + x: workUnit.metaData.lastRun.split('T')[0], + y: 1, + z: workUnit.finalStatus, + }); + } else { + newStackBarData.push({ x: workUnit.metaData.lastRun, y: 1, z: workUnit?.finalStatus }); + } + + // workUnitCountByBuild; + const { Build } = workUnit; + if (workUnitCountByBuild[Build]) { + workUnitCountByBuild[Build] = workUnitCountByBuild[Build] + 1; + } else { + workUnitCountByBuild[Build] = 1; + } + }); + + //--------------------------------------- + for (let key in workUnitCountByFinalStatus) { + newMetrics.push({ type: key, value: workUnitCountByFinalStatus[key] }); + } + //--------------------------------------- + for (let key in workUnitCountByInitialStatus) { + newDonutData.push({ type: key, value: workUnitCountByInitialStatus?.[key] }); + } + //--------------------------------------- + + //add titles + newStackBarData.push({ title: 'Count of Workunits by Date and Final Build Status' }); + newDonutData.push({ title: 'Count of Workunits by Initial Build Status' }); + newMetrics.push({ title: 'Count of Workunits by Final Build Status' }); + + setTitleMetrics(newTitleMetrics); + setMetrics(newMetrics); + setStackBarData(newStackBarData); + setDonutData(newDonutData); + } + }; + + //Step 4 (repeatable) - now we need to re-render the data when it filters change + useEffect(() => { + //if dashboard filters are set and filtered builds and workunits + if (Object.keys(dashboardFilters).length && filteredBuilds !== null && filteredWorkUnits !== null) { + filterData(); + } + }, [dashboardFilters]); + + return ( +
+ + + + + + + + + }> + +
+
+ +
+
+ + + {builds.length > 0 ? ( +
+ +
+ ) : loading ? ( +
+ +
+ ) : ( + + )} +
+
+
+
+ ); +} + +export default Orbit; diff --git a/client-reactjs/src/components/application/dashboard/Orbit/OrbitTable.jsx b/client-reactjs/src/components/application/dashboard/Orbit/OrbitTable.jsx new file mode 100644 index 000000000..54dc863c6 --- /dev/null +++ b/client-reactjs/src/components/application/dashboard/Orbit/OrbitTable.jsx @@ -0,0 +1,77 @@ +/* eslint-disable unused-imports/no-unused-vars */ +import React from 'react'; +import { Table } from 'antd'; + +function OrbitTable({ filteredWorkUnits, filteredBuilds, loading }) { + //Table columns and data + const columns = [ + { + title: 'Product', + render: (record) => { + return record.product?.toUpperCase(); + }, + width: 225, + }, + { title: 'Orbit Build Name', dataIndex: 'build' }, + { + title: 'WUs', + render: (record) => { + return record?.count ? record.count : 0; + }, + sorter: (a, b) => a.count - b.count, + + sortDirections: ['ascend', 'descend'], + defaultSortOrder: 'descend', + width: 75, + }, + ]; + + const wuColumns = [ + { title: 'Build WUID', dataIndex: 'wuid', width: 75 }, + { title: 'Version', dataIndex: 'version', width: 75 }, + { title: 'Initial Status', dataIndex: 'initialStatus', width: 100 }, + { title: 'Final Status', dataIndex: 'finalStatus', width: 150 }, + { title: 'Build Owner', dataIndex: 'primaryContact', width: 150 }, + ]; + + //JSX + return ( + <> +
+
+
record.name} + verticalAlign="top" + loading={loading} + pagination={false} + headerColor="white" + headerBg="#001529" + scroll={{ y: 400 }} + /> + +
+
+
record.key} + verticalAlign="top" + loading={loading} + headerColor="white" + headerBg="#001529" + scroll={{ x: 1300, y: 400 }} + pagination={false} + /> + + + + ); +} + +export default OrbitTable; diff --git a/client-reactjs/src/components/application/dashboard/notifications/charts/Donut.jsx b/client-reactjs/src/components/application/dashboard/common/charts/Donut.jsx similarity index 100% rename from client-reactjs/src/components/application/dashboard/notifications/charts/Donut.jsx rename to client-reactjs/src/components/application/dashboard/common/charts/Donut.jsx diff --git a/client-reactjs/src/components/application/dashboard/common/charts/MetricBoxes.jsx b/client-reactjs/src/components/application/dashboard/common/charts/MetricBoxes.jsx new file mode 100644 index 000000000..e50e24a54 --- /dev/null +++ b/client-reactjs/src/components/application/dashboard/common/charts/MetricBoxes.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Card } from 'antd'; +import _ from 'lodash'; + +function MetricBoxes({ metrics, bordered, headStyle }) { + return ( +
+ {metrics.map((metric) => { + const { title } = metric; + return ( + + {metric.description} + + ); + })} +
+ ); +} + +export default MetricBoxes; diff --git a/client-reactjs/src/components/application/dashboard/notifications/charts/NotificationCharts.jsx b/client-reactjs/src/components/application/dashboard/common/charts/NotificationCharts.jsx similarity index 100% rename from client-reactjs/src/components/application/dashboard/notifications/charts/NotificationCharts.jsx rename to client-reactjs/src/components/application/dashboard/common/charts/NotificationCharts.jsx diff --git a/client-reactjs/src/components/application/dashboard/notifications/charts/Pie.jsx b/client-reactjs/src/components/application/dashboard/common/charts/Pie.jsx similarity index 100% rename from client-reactjs/src/components/application/dashboard/notifications/charts/Pie.jsx rename to client-reactjs/src/components/application/dashboard/common/charts/Pie.jsx diff --git a/client-reactjs/src/components/application/dashboard/notifications/charts/StackedBar.jsx b/client-reactjs/src/components/application/dashboard/common/charts/StackedBar.jsx similarity index 100% rename from client-reactjs/src/components/application/dashboard/notifications/charts/StackedBar.jsx rename to client-reactjs/src/components/application/dashboard/common/charts/StackedBar.jsx diff --git a/client-reactjs/src/components/application/dashboard/common/charts/WorkUnitCharts.jsx b/client-reactjs/src/components/application/dashboard/common/charts/WorkUnitCharts.jsx new file mode 100644 index 000000000..d5c571b12 --- /dev/null +++ b/client-reactjs/src/components/application/dashboard/common/charts/WorkUnitCharts.jsx @@ -0,0 +1,35 @@ +/* eslint-disable prettier/prettier */ +import React from 'react'; +import { Resizable } from 're-resizable'; +// import Pie from './Pie'; +import StackedBar from './StackedBar'; +import Donut from './Donut'; + +function WorkUnitCharts({ metrics, stackBarData, groupDataBy, donutData }) { + return ( +
+ +
+

{donutData[donutData.length - 1]?.title}

+ {} +
+
+ + +
+

{metrics[metrics.length - 1]?.title}

+ {}{' '} +
+
+ + +
+

{stackBarData[stackBarData.length - 1]?.title}

+ {} +
+
+
+ ); +} + +export default WorkUnitCharts; diff --git a/client-reactjs/src/components/application/dashboard/notifications/index.css b/client-reactjs/src/components/application/dashboard/common/css/index.css similarity index 100% rename from client-reactjs/src/components/application/dashboard/notifications/index.css rename to client-reactjs/src/components/application/dashboard/common/css/index.css diff --git a/client-reactjs/src/components/application/dashboard/notifications/monitoringStatusOptions.js b/client-reactjs/src/components/application/dashboard/common/monitoringStatusOptions.js similarity index 100% rename from client-reactjs/src/components/application/dashboard/notifications/monitoringStatusOptions.js rename to client-reactjs/src/components/application/dashboard/common/monitoringStatusOptions.js diff --git a/client-reactjs/src/components/application/dashboard/notifications/BulkActions.jsx b/client-reactjs/src/components/application/dashboard/notifications/BulkActions.jsx index 77de33c68..e3e6417fc 100644 --- a/client-reactjs/src/components/application/dashboard/notifications/BulkActions.jsx +++ b/client-reactjs/src/components/application/dashboard/notifications/BulkActions.jsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { Modal, Select, Form, Button, message, Input } from 'antd'; import { authHeader } from '../../../common/AuthHeader'; -import { monitoringStatusOptions } from './monitoringStatusOptions.js'; +import { monitoringStatusOptions } from '../common/monitoringStatusOptions.js'; import { v4 as uuidv4 } from 'uuid'; import { useForm } from 'antd/lib/form/Form'; diff --git a/client-reactjs/src/components/application/dashboard/notifications/charts/Filters.jsx b/client-reactjs/src/components/application/dashboard/notifications/Filters.jsx similarity index 84% rename from client-reactjs/src/components/application/dashboard/notifications/charts/Filters.jsx rename to client-reactjs/src/components/application/dashboard/notifications/Filters.jsx index 092d649d8..9a1dccf4d 100644 --- a/client-reactjs/src/components/application/dashboard/notifications/charts/Filters.jsx +++ b/client-reactjs/src/components/application/dashboard/notifications/Filters.jsx @@ -3,16 +3,18 @@ import { Form, Select, DatePicker, Button, message } from 'antd'; import { useHistory, useLocation } from 'react-router-dom'; import moment from 'moment'; -import { authHeader, handleError } from '../../../../common/AuthHeader.js'; -import { monitoringStatusOptions } from '../monitoringStatusOptions.js'; -import '../index.css'; +import { authHeader, handleError } from '../../../common/AuthHeader.js'; +import { monitoringStatusOptions } from '../common/monitoringStatusOptions.js'; +import '../common/css/index.css'; -//Monitoring types options -const monitoringTypeOptions = [ +//Monitoring types options for notifications +const monitoringTypeOptionsForNotifications = [ { label: 'Job', value: 'jobMonitoring' }, { label: 'File', value: 'file' }, { label: 'Cluster', value: 'cluster' }, { label: 'Super File', value: 'superFile' }, + { label: 'Megaphone', value: 'megaphone' }, + { label: 'Orbit Monitoring', value: 'orbitMonitoring' }, ]; // Group by options @@ -30,11 +32,19 @@ const layout = { wrapperCol: { span: 24 }, }; -function Filters({ applicationId, setNotifications, setLoadingData, groupDataBy, setGroupDataBy, setDefaultFilters }) { +function Filters({ + applicationId, + setNotifications, + setLoadingData, + groupDataBy, + setGroupDataBy, + setDefaultFilters, + isOrbit, +}) { const [form] = Form.useForm(); const [dashboardFilters, setDashboardFilters] = useState({}); const initialValues = { - monitoringType: ['jobMonitoring', 'file', 'cluster', 'superFile'], + monitoringType: ['jobMonitoring', 'file', 'cluster', 'superFile', 'megaphone', 'orbitMonitoring'], monitoringStatus: ['notified', 'triage', 'inProgress', 'completed'], dateRange: [moment().subtract(15, 'days'), moment()], groupDataBy: groupDataBy, @@ -123,17 +133,20 @@ function Filters({ applicationId, setNotifications, setLoadingData, groupDataBy, return (
- - { + setDashboardFilters((prev) => ({ ...prev, monitoringType: values })); + updateParams({ monitoringType: values }); + }} + /> + + + ) : null} { + setMonitoringDetails({ + ...monitoringDetails, + businessUnit: value, + }); + }}> + + + + + + + + + + + +
+ loadOrbitBuildSuggestions(searchText)} + value={selectedOrbitBuild} + status={loading ? 'warning' : null}> + + + + + + {orbitBuildDetails ? ( + <> + {displayBuildInfo ? ( + <> +
+ Build Details +
+
+ +
+ + ) : null} + + ) : null} + + ); +}; + +export default BasicTab; diff --git a/client-reactjs/src/components/application/orbitMonitoring/MonitoringTab.jsx b/client-reactjs/src/components/application/orbitMonitoring/MonitoringTab.jsx new file mode 100644 index 000000000..2d75796a1 --- /dev/null +++ b/client-reactjs/src/components/application/orbitMonitoring/MonitoringTab.jsx @@ -0,0 +1,312 @@ +import React, { useState, useEffect } from 'react'; +import { Form, Input, Typography, Checkbox, Select } from 'antd'; +import cronstrue from 'cronstrue'; +import InfoDrawer from '../../common/InfoDrawer'; +import { InfoCircleOutlined } from '@ant-design/icons'; + +const { Title, Text } = Typography; + +const MonitoringTab = ({ + entryForm, + cron, + setCron, + monitoringDetails, + setMonitoringDetails, + selectedOrbitBuildDetails, + orbitBuildList, + editing, +}) => { + const [cronExplainer, setCronExplainer] = useState(null); + const [updateInterval, setUpdateInterval] = useState(null); + const [updateIntervalDays, setUpdateIntervalDays] = useState(null); + const [buildStatus, setBuildStatus] = useState(null); + const [severityCode, setSeverityCode] = useState(null); + const [required, setRequired] = useState(true); + const [open, setOpen] = useState(false); + + //TODO, try and get rid of this flag + const [updatedLocals, setUpdatedLocals] = useState(false); + + //update required state if update interval or update interval days is changed, use this to validate that one or the other is filled at least + useEffect(() => { + const result = updateInterval || updateIntervalDays; + setRequired(result ? false : true); + + if (editing && selectedOrbitBuildDetails && !updateInterval && !updateIntervalDays && !updatedLocals) { + setUpdateInterval(selectedOrbitBuildDetails?.metaData?.monitoringCondition?.updateInterval || null); + setUpdateIntervalDays(selectedOrbitBuildDetails?.metaData?.monitoringCondition?.updateIntervalDays || null); + //set this flag so it won't try and reset locals again and again preventing fields from being disabled + setUpdatedLocals(true); + } + entryForm.validateFields(['updateInterval', 'updateIntervalDays']); + }, [updateInterval, updateIntervalDays]); + + const showDrawer = () => { + setOpen(true); + }; + + const onClose = () => { + setOpen(false); + }; + + useEffect(() => { + entryForm.setFieldsValue({ + isActive: monitoringDetails.isActive, + }); + }, []); + + const notificationConditions = [ + { label: 'Build Status', value: 'buildStatus' }, + { label: 'Build Interval', value: 'updateInterval' }, + ]; + + const severityCodes = [ + { label: '0', value: 0 }, + { label: '1', value: 1 }, + { label: '2', value: 2 }, + { label: '3', value: 3 }, + ]; + + const daysOfTheWeek = [ + { label: 'Monday', value: 'monday' }, + { label: 'Tuesday', value: 'tuesday' }, + { label: 'Wednesday', value: 'wednesday' }, + { label: 'Thursday', value: 'thursday' }, + { label: 'Friday', value: 'friday' }, + { label: 'Saturday', value: 'saturday' }, + { label: 'Sunday', value: 'sunday' }, + ]; + + const buildStatuses = [ + { label: 'BUILD_AVAILABLE_FOR_USE', value: 'build_available_for_use' }, + { label: 'DISCARDED', value: 'discarded' }, + { label: 'FAILED_QA_QAHELD', value: 'failed_qa_qaheld' }, + { label: 'GRAVEYARD', value: 'graveyard' }, + { label: 'PASSED_QA', value: 'passed_qa' }, + { label: 'PASSED_QA_NO_RELEASE', value: 'passed_qa_no_release' }, + { label: 'PRODUCTION', value: 'production' }, + { label: 'SKIPPED', value: 'skipped' }, + ]; + + useEffect(() => { + if (cron) { + try { + const explainer = cronstrue.toString(cron); + entryForm.setFields([ + { + name: 'cron', + errors: null, + }, + ]); + setCronExplainer({ valid: true, message: `Runs ${explainer.toLocaleLowerCase()}` }); + } catch (err) { + setCronExplainer(null); + entryForm.setFields([ + { + name: 'cron', + errors: [err.split(':')[1]], + }, + ]); + } + } + if (!cron) setCronExplainer(null); + }, [cron]); + + return ( + <> + { + // only validate if not viewing other file details + if (editing) { + return Promise.resolve(); + } else { + const nameExists = orbitBuildList.find((Monitoring) => Monitoring.name === value); + + // if (nameExists || selectedOrbitBuildMonitoringDetails) { + if (!nameExists) { + return Promise.resolve(); + } else { + return Promise.reject(); + } + } + }, + }, + ]}> + + + +

+ Cron (How often to monitor) + showDrawer()} /> +

+ + + } + style={{ width: 'calc(47.5% - 8px)' }} + onChange={(e) => setMonitoringDetails({ ...monitoringDetails, cron: e.target.value })} + name="cron" + rules={[ + { required: true, message: 'Required field' }, + { + validator: async (_, cron) => { + if (cron) { + const cronInArray = cron.split(' '); + if (cronInArray.length > 5) { + setCronExplainer({ valid: false, message: `` }); + return Promise.reject('Cron expression has more than 5 parts'); + } else { + cronstrue.toString(cron); + } + } + }, + }, + ]} + extra={ + cronExplainer ? ( + {cronExplainer.message} + ) : ( + + Click here to create cron expression + + ) + }> + { + setCron(e.target.value); + }} + /> +
+ + + + + + + + + + {monitoringDetails.monitoringConditions.includes('updateInterval') ? ( +
+ + <Text type="danger">*</Text> Build Interval: (choose one) + +
+ + { + setUpdateInterval(e.target.value); + }}> + + + + + +
+
+ ) : null} + + {monitoringDetails.monitoringConditions.includes('buildStatus') ? ( + <> + + + + + ) : null} + + + Start monitoring now + + + ); +}; + +export default MonitoringTab; diff --git a/client-reactjs/src/components/application/orbitMonitoring/NotificationsTab.jsx b/client-reactjs/src/components/application/orbitMonitoring/NotificationsTab.jsx new file mode 100644 index 000000000..9b5b06254 --- /dev/null +++ b/client-reactjs/src/components/application/orbitMonitoring/NotificationsTab.jsx @@ -0,0 +1,162 @@ +import React, { useState } from 'react'; +import { Form, Select, Input, Button } from 'antd'; +import { MinusCircleOutlined, PlusOutlined, InfoCircleOutlined } from '@ant-design/icons'; +import InfoDrawer from '../../common/InfoDrawer'; + +const notificationOptions = [ + { label: 'E-mail', value: 'eMail' }, + { label: 'MS Teams', value: 'msTeams' }, +]; + +const NotificationTab = ({ setNotificationDetails, notificationDetails }) => { + // useEffect(() => {}, [selectedFileMonitoringDetails]); + const [open, setOpen] = useState(false); + + const showDrawer = () => { + setOpen(true); + }; + + const onClose = () => { + setOpen(false); + }; + + return ( + <> + + + + + + + + +

+ Notification Channel + showDrawer()} /> +

+ + + } + name="notificationChannels" + rules={[{ required: true, message: 'Required Field' }]}> + +
+ + {notificationDetails?.notificationChannel?.includes('eMail') ? ( + + {(fields, { add, remove }) => ( + <> + {fields.map((field, _index) => ( + +
+ + + + {fields.length > 1 ? ( + remove(field.name)} + style={{ marginLeft: '10px' }} + /> + ) : null} +
+
+ ))} + + + + + )} +
+ ) : null} + + {notificationDetails?.notificationChannel?.includes('msTeams') ? ( + + {(fields, { add, remove }) => ( + <> + {fields.map((field, _index) => ( + +
+ + + + {fields.length > 1 ? ( + remove(field.name)} + style={{ marginLeft: '10px' }} + /> + ) : null} +
+
+ ))} + + + + + )} +
+ ) : null} + + ); +}; + +export default NotificationTab; diff --git a/client-reactjs/src/components/application/orbitMonitoring/OrbitMonitoring.jsx b/client-reactjs/src/components/application/orbitMonitoring/OrbitMonitoring.jsx new file mode 100644 index 000000000..01f15d053 --- /dev/null +++ b/client-reactjs/src/components/application/orbitMonitoring/OrbitMonitoring.jsx @@ -0,0 +1,120 @@ +import React, { useState, useEffect } from 'react'; +import BreadCrumbs from '../../common/BreadCrumbs'; +import { Tooltip, Button, message } from 'antd'; +import Text from '../../common/Text'; +import OrbitMonitoringTable from './OrbitMonitoringTable'; +import OrbitMonitoringModal from './OrbitMonitoringModal'; +import { authHeader, handleError } from '../../common/AuthHeader.js'; +import { useSelector } from 'react-redux'; + +const OrbitMonitoring = () => { + const [modalVisible, setModalVisible] = useState(false); + const [confirmLoading, setConfirmLoading] = useState(false); + const [orbitBuildList, setOrbitBuildList] = useState(null); + const [selectedOrbitBuild, setSelectedOrbitBuild] = useState(null); + const [editing, setEditing] = useState(null); + + const { + application: { applicationId }, + } = useSelector((state) => state.applicationReducer); + + const addOrbitMonitoring = async () => { + await setSelectedOrbitBuild(null); + await setEditing(null); + await setConfirmLoading(true); + await setModalVisible(true); + await setConfirmLoading(false); + }; + + useEffect(() => { + async function fetchBuilds() { + if (applicationId) { + await getOrbitMonitoring(applicationId); + } + } + fetchBuilds(); + }, [applicationId]); + + //Get list of all orbit monitoring + const getOrbitMonitoring = async (applicationId) => { + try { + const payload = { + method: 'GET', + header: authHeader(), + }; + + const response = await fetch(`/api/orbit/allMonitorings/${applicationId}`, payload); + if (!response.ok) handleError(response); + + const data = await response.json(); + + setOrbitBuildList(data); + } catch (err) { + console.log(err); + } + }; + + const saveOrbitBuildDetails = async (monitoringDetails) => { + try { + const payload = { + method: editing ? 'PUT' : 'POST', + header: authHeader(), + body: JSON.stringify({ + ...monitoringDetails, + id: editing ? editing : selectedOrbitBuild, + build: selectedOrbitBuild, + }), + }; + + console.log(monitoringDetails); + + const response = await fetch('/api/orbit/', payload); + + if (!response.ok) { + handleError(response); + } else { + message.success('Successfully saved orbit build monitoring data'); + getOrbitMonitoring(); + } + } catch (error) { + console.log(error); + message.error('Failed to save orbit build monitoring'); + } + }; + + return ( + <> + + + + } + /> + + + + ); +}; + +export default OrbitMonitoring; diff --git a/client-reactjs/src/components/application/orbitMonitoring/OrbitMonitoringModal.jsx b/client-reactjs/src/components/application/orbitMonitoring/OrbitMonitoringModal.jsx new file mode 100644 index 000000000..5b6c6dbe0 --- /dev/null +++ b/client-reactjs/src/components/application/orbitMonitoring/OrbitMonitoringModal.jsx @@ -0,0 +1,351 @@ +import React, { useState, useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import { Modal, Button, Tabs, Form, message, Spin } from 'antd'; +import BasicTab from './BasicTab'; +import MonitoringTab from './MonitoringTab'; +import NotificationsTab from './NotificationsTab'; +import useWindowSize from '../../../hooks/useWindowSize'; +import { authHeader, handleError } from '../../common/AuthHeader.js'; + +const { TabPane } = Tabs; + +const OrbitMonitoringModal = ({ + modalVisible, + orbitBuildList, + setModalVisible, + saveOrbitBuildDetails, + selectedOrbitBuild, + setSelectedOrbitBuild, + editing, + setEditing, + getOrbitMonitoring, +}) => { + //modal states + const [modalWidth, setModalWidth] = useState(0); + const [activeTab, setActiveTab] = useState('1'); + const [cron, setCron] = useState(null); + const windowSize = useWindowSize(); + const [orbitBuildDetails, setOrbitBuildDetails] = useState(null); + const [monitoringDetails, setMonitoringDetails] = useState({ + isActive: true, + monitoringConditions: [], + }); + const [selectedOrbitBuildDetails, setSelectedOrbitBuildDetails] = useState(null); + const [notificationDetails, setNotificationDetails] = useState({}); + const [entryForm] = Form.useForm(); + const [confirmLoading, setConfirmLoading] = useState(false); + const [fetchingOrbitDetails, setFetchingOrbitDetails] = useState(false); + + const { + application: { applicationId }, + } = useSelector((state) => state.applicationReducer); + + //set fields of form if monitoring is selected + useEffect(() => { + if (!editing) { + return; + } + if (!selectedOrbitBuildDetails) { + getOrbitBuildDetails(selectedOrbitBuild); + } + + //if details have been gotten, set field values + if (selectedOrbitBuildDetails) { + //grab and restructure notification channels to set into fields + let notificationChannels = []; + let emails; + let msTeams; + + if (selectedOrbitBuildDetails.metaData && selectedOrbitBuildDetails.metaData.notifications) { + for (let i = 0; i < selectedOrbitBuildDetails.metaData.notifications.length; i++) { + if (selectedOrbitBuildDetails.metaData.notifications[i].channel === 'eMail') { + emails = selectedOrbitBuildDetails.metaData.notifications[i].recipients; + notificationChannels.push('eMail'); + } + + if (selectedOrbitBuildDetails.metaData.notifications[i].channel === 'msTeams') { + msTeams = selectedOrbitBuildDetails.metaData.notifications[i].recipients; + notificationChannels.push('msTeams'); + } + } + } + + if (selectedOrbitBuildDetails.metaData) { + const { + name, + cron, + isActive, + build, + severityCode, + product, + businessUnit, + host, + primaryContact, + secondaryContact, + metaData: { + monitoringCondition: { notifyCondition, updateInterval, updateIntervalDays, buildStatus, deleted }, + }, + } = selectedOrbitBuildDetails; + + entryForm.setFieldsValue({ + name: name, + monitorName: name, + cron: cron, + build: build, + isActive: isActive, + notificationChannels: notificationChannels, + severityCode: severityCode, + notifyCondition: notifyCondition, + businessUnit: businessUnit, + product: product, + host: host, + primaryContact: primaryContact, + secondaryContact: secondaryContact, + updateInterval: updateInterval, + updateIntervalDays: updateIntervalDays, + buildStatus: buildStatus, + deleted: deleted, + emails: emails, + msTeamsGroups: msTeams, + }); + + setSelectedOrbitBuild(build); + + //set states to have fields appear that are necessary + setCron(cron); + setMonitoringDetails({ + ...monitoringDetails, + monitoringConditions: notifyCondition, + }); + setNotificationDetails({ ...notificationDetails, notificationChannel: notificationChannels }); + } + } + }, [selectedOrbitBuildDetails, selectedOrbitBuild]); + + //validate forms before saving + const validateForms = async () => { + let validationError = null; + let formData = {}; + + try { + formData = await entryForm.validateFields(); + } catch (err) { + validationError = err; + } + + return { validationError, formData }; + }; + + //save data + const handleSave = async () => { + try { + setConfirmLoading(true); + //validate data and throw new error + + const data = await validateForms(); + + if (data.validationError?.errorFields) { + throw new Error('Validation failed, please check form fields again.'); + } + + const formData = data.formData; + + let notificationChannels = formData.notificationChannels; + let emails = formData.emails; + let msTeamsGroups = formData.msTeamsGroups; + + formData.application_id = applicationId; + + // current UTC Time Stamp + const date = new Date(); + const currentTimeStamp = date.getTime(); + + let notifyCondition = formData.notifyCondition; + let updateInterval = formData.updateInterval; + let updateIntervalDays = formData.updateIntervalDays; + let buildStatus = formData.buildStatus; + let deleted = formData.deleted; + + //organize notifications + let notifications = []; + for (let i = 0; i < notificationChannels.length; i++) { + if (notificationChannels[i] == 'eMail') { + notifications.push({ channel: 'eMail', recipients: emails }); + } + if (notificationChannels[i] == 'msTeams') { + notifications.push({ channel: 'msTeams', recipients: msTeamsGroups }); + } + } + + formData.metaData = { + lastMonitored: currentTimeStamp, + monitoringCondition: { + notifyCondition, + updateInterval, + updateIntervalDays, + buildStatus, + deleted, + }, + notifications, + isActive: formData.isActive, + }; + + console.log(formData); + + await saveOrbitBuildDetails(formData); + cancelModal(); + } catch (err) { + setConfirmLoading(false); + console.log(err.message); + if (err.message !== 'Validation failed') message.error(err.message); + } + }; + + const cancelModal = async () => { + await setEditing(null); + await setOrbitBuildDetails(null); + await setSelectedOrbitBuild(null); + await setSelectedOrbitBuildDetails(null); + entryForm.resetFields(); + + await setModalVisible(false); + await setActiveTab('1'); + await setConfirmLoading(false); + await setMonitoringDetails({ + isActive: true, + monitoringConditions: [], + }); + await getOrbitMonitoring(applicationId); + }; + + // Changes modal size per screen vw + useEffect(() => { + const { width } = windowSize.inner; + if (width > 1500) { + setModalWidth('40vw'); + } else if (width > 1000) { + setModalWidth('60vw'); + } else { + setModalWidth('100vw'); + } + }, [windowSize]); + + //Modal footer btns ------------------------------------------------------------------------- + const nextBtn = ( + + ); + + // Get details of a orbit monitoring ----------------------------------------------- + const getOrbitBuildDetails = async (id) => { + try { + const payload = { method: 'GET', header: authHeader() }; + setFetchingOrbitDetails(true); + const response = await fetch(`/api/orbit/getOne/${applicationId}/${id}`, payload); + if (!response.ok) handleError(response); + const data = await response.json(); + + setActiveTab('2'); + setOrbitBuildDetails(data); + + //if selected monitoring, store selected orbit monitoring in different state for later + if (selectedOrbitBuild) { + await setSelectedOrbitBuildDetails(data); + } + } catch (err) { + setFetchingOrbitDetails(false); + } finally { + setFetchingOrbitDetails(false); + } + }; + + const backBtn = ( + + ); + + const saveBtn = ( + + ); + const btns = { + 0: null, + 1: [nextBtn], + 2: [backBtn, nextBtn], + 3: [backBtn, saveBtn], + }; + + return ( + + {fetchingOrbitDetails ? ( +
+ +
+ ) : ( + + { + setActiveTab(record); + }}> + + + + + + + + + + + + )} +
+ ); +}; + +export default OrbitMonitoringModal; diff --git a/client-reactjs/src/components/application/orbitMonitoring/OrbitMonitoringTable.jsx b/client-reactjs/src/components/application/orbitMonitoring/OrbitMonitoringTable.jsx new file mode 100644 index 000000000..a16984475 --- /dev/null +++ b/client-reactjs/src/components/application/orbitMonitoring/OrbitMonitoringTable.jsx @@ -0,0 +1,120 @@ +import React from 'react'; +import { Table, Space, Tooltip, Badge, message } from 'antd'; +import { DeleteOutlined, EyeOutlined, PauseCircleOutlined, PlayCircleOutlined } from '@ant-design/icons'; +import { authHeader, handleError } from '../../common/AuthHeader.js'; + +const OrbitMonitoringTable = ({ + orbitBuildList, + setOrbitBuildList, + setSelectedOrbitBuild, + setModalVisible, + setEditing, +}) => { + // Delete record + const deleteOrbitBuildMonitoring = async ({ id, name }) => { + try { + const payload = { + method: 'DELETE', + header: authHeader(), + }; + + const response = await fetch(`/api/orbit/delete/${id}/${name}`, payload); + console.log(response); + if (!response.ok) return handleError(response); + + const newOrbitBuildMonitoringList = orbitBuildList.filter((OrbitBuild) => OrbitBuild.id != id); + setOrbitBuildList(newOrbitBuildMonitoringList); + } catch (err) { + message.error('Failed to delete Orbit Build monitoring ', err.message); + } + }; + + // View existing orbit monitoring ------------------------------------------------------------------ + const viewExistingOrbitBuildMonitoring = (id) => { + setEditing(id); + setModalVisible(true); + setSelectedOrbitBuild(id); + }; + + const changeOrbitBuildMonitoringStatus = async (id) => { + try { + const payload = { + method: 'PUT', + header: authHeader(), + }; + + const response = await fetch(`/api/orbit/toggleStatus/${id}`, payload); + if (!response.ok) return handleError(response); + const updatedMonitoringList = orbitBuildList.map((record) => + record.id === id ? { ...record, isActive: !record.isActive } : record + ); + setOrbitBuildList(updatedMonitoringList); + } catch (err) { + message.error('Failed to update file monitoring status'); + } + }; + + const columns2 = [ + { + title: 'Status', + render: (_, record) => ( + <> + + + ), + }, + { title: 'Name', dataIndex: 'name' }, + { title: 'Build', dataIndex: 'build' }, + + { title: 'Cron', dataIndex: 'cron' }, + { title: 'Severity Code', dataIndex: 'severityCode' }, + { + title: 'Delete', + dataIndex: 'delete', + render: (_, record) => ( + + + + viewExistingOrbitBuildMonitoring(record.id)} /> + + + {record.isActive ? ( + + + changeOrbitBuildMonitoringStatus(record.id)} /> + + + ) : ( + + + changeOrbitBuildMonitoringStatus(record.id)} /> + + + )} + + + { + deleteOrbitBuildMonitoring({ id: record.id, name: record.name }); + }} + /> + + + + ), + }, + ]; + return ( + <> +
record.id} + /> + + ); +}; + +export default OrbitMonitoringTable; diff --git a/client-reactjs/src/components/common/Constants.js b/client-reactjs/src/components/common/Constants.js index b16c5ee9d..a05556850 100644 --- a/client-reactjs/src/components/common/Constants.js +++ b/client-reactjs/src/components/common/Constants.js @@ -40,6 +40,8 @@ export const Constants = { LICENSES_RETRIEVED: 'LICENSES_RETRIEVED', CONSTRAINTS_RETRIEVED: 'CONSTRAINTS_RETRIEVED', UPDATE_CONSTRAINTS: 'UPDATE_CONSTRAINTS', + UPDATE_INTEGRATIONS: 'UPDATE_INTEGRATIONS', + INTEGRATIONS_RETRIEVED: 'INTEGRATIONS_RETRIEVED', PROPAGATIONS_CHANGES_INITIATE: 'PROPAGATIONS_CHANGES_INITIATE', PROPAGATIONS_CHANGES_SUCCESS: 'PROPAGATIONS_CHANGES_SUCCESS', diff --git a/client-reactjs/src/components/layout/Header.js b/client-reactjs/src/components/layout/Header.js index e62cd4576..0b237208f 100644 --- a/client-reactjs/src/components/layout/Header.js +++ b/client-reactjs/src/components/layout/Header.js @@ -143,6 +143,7 @@ class AppHeader extends Component { this.props.dispatch(applicationActions.getConsumers()); this.props.dispatch(applicationActions.getLicenses()); this.props.dispatch(applicationActions.getConstraints()); + this.props.dispatch(applicationActions.getIntegrations(this.props.application.applicationId)); } if (this.props.newApplication) { diff --git a/client-reactjs/src/components/layout/LeftNav.js b/client-reactjs/src/components/layout/LeftNav.js index cfb4e6a36..9ed30fe14 100644 --- a/client-reactjs/src/components/layout/LeftNav.js +++ b/client-reactjs/src/components/layout/LeftNav.js @@ -11,6 +11,8 @@ import { ClockCircleOutlined, ContainerOutlined, BarChartOutlined, + CloudServerOutlined, + ApiOutlined, BellOutlined, } from '@ant-design/icons'; @@ -59,6 +61,9 @@ class LeftNav extends Component { render() { const applicationId = this.props?.applicationId || ''; + const integrations = this.props?.integrations || []; + + const orbitActive = integrations.find((i) => i.name === 'Orbit')?.active; if (!this.props.loggedIn || !this.props.user || Object.getOwnPropertyNames(this.props.user).length == 0) { return null; @@ -118,6 +123,12 @@ class LeftNav extends Component { }> {} + + {orbitActive ? ( + }> + {} + + ) : null} } key="5"> @@ -127,6 +138,12 @@ class LeftNav extends Component { }> {} + + {orbitActive ? ( + }> + {} + + ) : null} {canEdit ? ( @@ -163,8 +180,11 @@ class LeftNav extends Component { }> {} + }> + {} + : }> Compliance @@ -179,10 +199,11 @@ class LeftNav extends Component { } function mapStateToProps(state) { + const integrations = state.applicationReducer.integrations; const applicationId = state.applicationReducer.application?.applicationId; const { loggedIn, user } = state.authenticationReducer; const isReportLoading = state.propagation.changes.loading || state.propagation.current.loading; - return { applicationId, loggedIn, user, isReportLoading }; + return { applicationId, integrations, loggedIn, user, isReportLoading }; } let connectedLeftNav = connect(mapStateToProps, null, null, { forwardRef: true })(withRouter(LeftNav)); diff --git a/client-reactjs/src/index.css b/client-reactjs/src/index.css index 7c5860c79..89b7cc661 100644 --- a/client-reactjs/src/index.css +++ b/client-reactjs/src/index.css @@ -1073,3 +1073,37 @@ div.tooltip { .sentNotificationBody p:last-child { display: none; } + +.OrbitTable .ant-table-thead > tr > th { + background: #002140; + color: white; +} + +.OrbitTable .ant-table-thead > tr > .ant-table-column-sort:hover { + color: black; +} + +.OrbitTable .ant-table-cell-scrollbar { + box-shadow: none; +} + +.OrbitTable tr:nth-child(2n) { + background: lightgrey; +} + +.OrbitTable tr:nth-child(2n) { + background: lightgrey; +} + +.OrbitTable tr:nth-child(2n) { + background: lightgrey; +} + +.OrbitTable .ant-table-row-level-0:nth-child(2n) .ant-table-column-sort { + background: lightgrey; +} + +.chartLabel { + color: rgb(71, 70, 70); + margin-bottom: 2rem; +} diff --git a/client-reactjs/src/redux/actions/Application.js b/client-reactjs/src/redux/actions/Application.js index b4d59ec5e..eaccbfd9b 100644 --- a/client-reactjs/src/redux/actions/Application.js +++ b/client-reactjs/src/redux/actions/Application.js @@ -11,6 +11,8 @@ export const applicationActions = { getLicenses, getConstraints, updateConstraints, + getIntegrations, + updateIntegrations, }; function applicationSelected(applicationId, applicationTitle) { @@ -80,3 +82,16 @@ function getConstraints() { function updateConstraints(constraints) { return { type: Constants.UPDATE_CONSTRAINTS, constraints }; } + +function getIntegrations(applicationId) { + return (dispatch) => { + fetch(`/api/integrations/get/${applicationId}`, { headers: authHeader() }) + .then((response) => (response.ok ? response.json() : handleError(response))) + .then((integrations) => dispatch({ type: Constants.INTEGRATIONS_RETRIEVED, integrations })) + .catch(console.log); + }; +} + +function updateIntegrations(integrations) { + return { type: Constants.UPDATE_INTEGRATIONS, integrations }; +} diff --git a/client-reactjs/src/redux/reducers/ApplicationReducer.js b/client-reactjs/src/redux/reducers/ApplicationReducer.js index f131228bb..fc67121e9 100644 --- a/client-reactjs/src/redux/reducers/ApplicationReducer.js +++ b/client-reactjs/src/redux/reducers/ApplicationReducer.js @@ -9,6 +9,7 @@ const initialState = { consumers: [], licenses: [], constraints: [], + integrations: [], }; export function applicationReducer(state = initialState, action) { @@ -67,6 +68,16 @@ export function applicationReducer(state = initialState, action) { ...state, constraints: action.constraints, }; + case Constants.INTEGRATIONS_RETRIEVED: + return { + ...state, + integrations: action.integrations, + }; + case Constants.UPDATE_INTEGRATIONS: + return { + ...state, + integrations: action.integrations, + }; default: return state; } diff --git a/eclcc.log b/eclcc.log new file mode 100644 index 000000000..3a13810da --- /dev/null +++ b/eclcc.log @@ -0,0 +1,13 @@ +00000000 PRG 2024-01-30 08:05:30 21740 4624 "Error loading C:\Program Files\HPCCSystems\8.10.12\clienttools\filehooks\archive.dll: 126 - The specified module could not be found." +00000001 PRG 2024-01-30 08:05:30 21740 4624 "File hook library C:\Program Files\HPCCSystems\8.10.12\clienttools\filehooks\archive.dll could not be loaded" +00000002 PRG 2024-01-30 08:05:30 21740 4624 "Error loading C:\Program Files\HPCCSystems\8.10.12\clienttools\filehooks\archivefile.dll: 126 - The specified module could not be found." +00000003 PRG 2024-01-30 08:05:30 21740 4624 "File hook library C:\Program Files\HPCCSystems\8.10.12\clienttools\filehooks\archivefile.dll could not be loaded" +00000004 PRG 2024-01-30 08:05:31 21740 4624 "File hook library C:\Program Files\HPCCSystems\8.10.12\clienttools\filehooks\bz2.dll could not be loaded" +00000005 PRG 2024-01-30 08:05:31 21740 4624 "Error loading C:\Program Files\HPCCSystems\8.10.12\clienttools\filehooks\git2.dll: 126 - The specified module could not be found." +00000006 PRG 2024-01-30 08:05:31 21740 4624 "File hook library C:\Program Files\HPCCSystems\8.10.12\clienttools\filehooks\git2.dll could not be loaded" +00000007 PRG 2024-01-30 08:05:31 21740 4624 "Error loading C:\Program Files\HPCCSystems\8.10.12\clienttools\filehooks\gitfile.dll: 126 - The specified module could not be found." +00000008 PRG 2024-01-30 08:05:31 21740 4624 "File hook library C:\Program Files\HPCCSystems\8.10.12\clienttools\filehooks\gitfile.dll could not be loaded" +00000009 PRG 2024-01-30 08:05:31 21740 4624 "Error loading C:\Program Files\HPCCSystems\8.10.12\clienttools\filehooks\gitfile.lib: 193 - 193" +0000000A PRG 2024-01-30 08:05:31 21740 4624 "File hook library C:\Program Files\HPCCSystems\8.10.12\clienttools\filehooks\gitfile.lib could not be loaded" +0000000B PRG 2024-01-30 08:05:31 21740 4624 "File hook library C:\Program Files\HPCCSystems\8.10.12\clienttools\filehooks\pcre.dll could not be loaded" +0000000C PRG 2024-01-30 08:05:31 21740 4624 "File hook library C:\Program Files\HPCCSystems\8.10.12\clienttools\filehooks\zlib1.dll could not be loaded" diff --git a/server/.gitignore b/server/.gitignore index 34d11596b..c095a2709 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -33,3 +33,8 @@ cluster-whitelist.js eclcc.log logs/* +#timezone files +/eclDir + +#temp files +/tempFiles \ No newline at end of file diff --git a/server/job-scheduler.js b/server/job-scheduler.js index 4b33d7d18..d6c8e905f 100644 --- a/server/job-scheduler.js +++ b/server/job-scheduler.js @@ -44,8 +44,16 @@ const { scheduleFileMonitoring, } = require("./jobSchedularMethods/hpccFiles.js"); +const { + createOrbitMegaphoneJob, + createOrbitMonitoringJob, + scheduleOrbitMonitoringOnServerStart, +} = require("./jobSchedularMethods/orbitJobs.js"); + const { scheduleKeyCheck } = require("./jobSchedularMethods/apiKeys.js"); +//import job directly to run it only once on server start +const { createIntegrations } = require("./jobs/integrationCreation.js"); class JobScheduler { constructor() { @@ -115,6 +123,11 @@ class JobScheduler { await this.scheduleKeyCheck(); await this.scheduleJobMonitoringOnServerStart(); await this.createClusterUsageHistoryJob(); + await this.scheduleOrbitMonitoringOnServerStart(); + await this.createOrbitMegaphoneJob(); + + //one off jobs on server start + await this.createIntegrations(); })(); } @@ -311,6 +324,22 @@ class JobScheduler { scheduleKeyCheck() { return scheduleKeyCheck.call(this); } + + //orbit jobs + createOrbitMegaphoneJob() { + return createOrbitMegaphoneJob.call(this); + } + scheduleOrbitMonitoringOnServerStart() { + return scheduleOrbitMonitoringOnServerStart.call(this); + } + + createOrbitMonitoringJob({ orbitMonitoring_id, cron }) { + return createOrbitMonitoringJob.call(this, { orbitMonitoring_id, cron }); + } + + createIntegrations() { + return createIntegrations.call(this); + } } module.exports = new JobScheduler(); diff --git a/server/jobSchedularMethods/clusterJobs.js b/server/jobSchedularMethods/clusterJobs.js index ac1fdb4e3..1b06513eb 100644 --- a/server/jobSchedularMethods/clusterJobs.js +++ b/server/jobSchedularMethods/clusterJobs.js @@ -47,7 +47,7 @@ function createClusterMonitoringBreeJob({ clusterMonitoring_id, cron }) { const job = { cron, name: uniqueJobName, - path: path.join(__dirname, "jobs", SUBMIT_CLUSTER_MONITORING_JOB), + path: path.join(__dirname, "..", "jobs", SUBMIT_CLUSTER_MONITORING_JOB), worker: { workerData: { clusterMonitoring_id }, }, diff --git a/server/jobSchedularMethods/hpccFiles.js b/server/jobSchedularMethods/hpccFiles.js index 5a9af6d65..7bfa0bb3e 100644 --- a/server/jobSchedularMethods/hpccFiles.js +++ b/server/jobSchedularMethods/hpccFiles.js @@ -142,50 +142,6 @@ async function scheduleFileMonitoringOnServerStart() { } } -async function scheduleFileMonitoringOnServerStart() { - try { - const activeLandingZoneFileMonitoring = await FileMonitoring.findAll({ - where: { - monitoringActive: true, - // monitoringAssetType: "landingZoneFile", - }, - raw: true, - }); - for (const monitoring of activeLandingZoneFileMonitoring) { - await this.scheduleFileMonitoringBreeJob({ - filemonitoring_id: monitoring.id, - name: monitoring.name, - cron: monitoring.cron, - monitoringAssetType: monitoring.monitoringAssetType, - }); - } - } catch (err) { - logger.error(err); - } -} - -async function scheduleFileMonitoringOnServerStart() { - try { - const activeLandingZoneFileMonitoring = await FileMonitoring.findAll({ - where: { - monitoringActive: true, - // monitoringAssetType: "landingZoneFile", - }, - raw: true, - }); - for (const monitoring of activeLandingZoneFileMonitoring) { - await this.scheduleFileMonitoringBreeJob({ - filemonitoring_id: monitoring.id, - name: monitoring.name, - cron: monitoring.cron, - monitoringAssetType: monitoring.monitoringAssetType, - }); - } - } catch (err) { - logger.error(err); - } -} - async function scheduleFileMonitoring() { logger.info("📂 FILE MONITORING STARTED ..."); try { diff --git a/server/jobSchedularMethods/orbitJobs.js b/server/jobSchedularMethods/orbitJobs.js new file mode 100644 index 000000000..be57b40b2 --- /dev/null +++ b/server/jobSchedularMethods/orbitJobs.js @@ -0,0 +1,58 @@ +const path = require("path"); +const logger = require("../config/logger"); + +const models = require("../models"); +orbitMonitoring = models.orbitMonitoring; + +const MEGAPHONE_JOB = "orbitMegaphone.js"; +const ORBIT_MONITORING = "submitOrbitMonitoring.js"; + +function createOrbitMegaphoneJob() { + const uniqueJobName = `Orbit Megaphone Job`; + const job = { + interval: "30m", + name: uniqueJobName, + path: path.join(__dirname, "..", "jobs", MEGAPHONE_JOB), + }; + this.bree.add(job); + this.bree.start(uniqueJobName); + logger.info("📈 ORBIT MEGAPHONE JOB STARTED ..."); +} + +function createOrbitMonitoringJob({ orbitMonitoring_id, cron }) { + const uniqueJobName = `Orbit Monitoring - ${orbitMonitoring_id}`; + const job = { + cron, + name: uniqueJobName, + path: path.join(__dirname, "..", "jobs", ORBIT_MONITORING), + worker: { + workerData: { orbitMonitoring_id }, + }, + }; + this.bree.add(job); + this.bree.start(uniqueJobName); +} + +async function scheduleOrbitMonitoringOnServerStart() { + try { + logger.info("📺 ORBIT MONITORING STARTED ..."); + const orbitMonitorings = await orbitMonitoring.findAll({ raw: true }); + for (let monitoring of orbitMonitorings) { + const { id, cron, isActive } = monitoring; + if (isActive) { + this.createOrbitMonitoringJob({ + orbitMonitoring_id: id, + cron, + }); + } + } + } catch (err) { + logger.error(err); + } +} + +module.exports = { + createOrbitMegaphoneJob, + createOrbitMonitoringJob, + scheduleOrbitMonitoringOnServerStart, +}; diff --git a/server/jobs/integrationCreation.js b/server/jobs/integrationCreation.js new file mode 100644 index 000000000..eebb00ee4 --- /dev/null +++ b/server/jobs/integrationCreation.js @@ -0,0 +1,60 @@ +const { parentPort } = require("worker_threads"); + +const logger = require("../config/logger"); +const models = require("../models"); +const application = models.application; +const integrations = models.integrations; + +async function createIntegrations() { + try { + //grab all applications so we can have one entry per integration per application + const applications = await application.findAll({}); + + let integrationList = []; + + //build list of integrations + applications.map((application) => { + //for each application, add an object of each integration, for now we only have orbit + + if (process.env.ASR === "true") { + integrationList.push({ + application_id: application.id, + name: "Orbit", + description: + "Enabling this integration will allow Tombolo to collect data from HPCCs Orbit system and provide dashboard information for it", + active: "false", + metaData: { + notificationEmails: "matthew.fancher@lexisnexisrisk.com", + notificationWebhooks: + "https://reedelsevier.webhook.office.com/webhookb2/81c072d6-6b47-4eca-9434-73944c464876@9274ee3f-9425-4109-a27f-9fb15c10675d/IncomingWebhook/60019c7653734064b4c225a02b1da597/af40e12f-e839-4801-91e9-e61a20045feb", + }, + }); + } + }); + + await Promise.all( + //create integrations, double checking they don't already exist + integrationList.map(async (integration) => { + let exists = await integrations.findOne({ + where: { + name: integration.name, + application_id: integration.application_id, + }, + raw: true, + }); + + //if it doesn't exist, create integration + if (!exists) { + await integrations.create(integration); + } + return true; + }) + ); + } catch (error) { + logger.error("Failed to create integrations, error: " + error); + } finally { + if (parentPort) parentPort.postMessage("done"); + } +} + +module.exports = { createIntegrations }; diff --git a/server/jobs/messageCards/notificationTemplate.js b/server/jobs/messageCards/notificationTemplate.js index b200bd89d..b9fa6951b 100644 --- a/server/jobs/messageCards/notificationTemplate.js +++ b/server/jobs/messageCards/notificationTemplate.js @@ -28,7 +28,7 @@ module.exports = { body += `
${keys}: ${details[keys]}
`; } - if (tableRows !== "") { + if (tableRows !== "2") { body = body + table; } @@ -36,6 +36,314 @@ module.exports = { return body; }, + //orbit monitoring email body + orbitMonitoringEmailBody: function (buildDetails) { + //build out issue row + let issue = ``; + + if ( + buildDetails.issue.metaDifference && + buildDetails.issue.metaDifference.length > 0 + ) { + buildDetails.issue.metaDifference.forEach((meta) => { + issue += `Issue: ${meta.attribute}
`; + issue += `Detected Value: ${meta.newValue}
`; + }); + } + + issue += `HOST: ${buildDetails.issue.host}
`; + issue += `JOB NAME: ${buildDetails.issue.build}
`; + issue += `MONITORING SCHEDULE: ${buildDetails.issue.cron}
`; + issue += `RETURNED JOB DATE: ${buildDetails.date}
`; + issue += `RETURNED JOB STATE: ${buildDetails.issue.status}
`; + issue += `RETURNED JOB WUID: ${buildDetails.issue.workUnit}
`; + + let tableRows = `
+ + + + + + + `; + + const table = `
PRODUCT${buildDetails.product.toUpperCase()}
ISSUE${issue}
SEV_CODE${ + buildDetails.severityCode + }
DATE_DETECTED${ + buildDetails.date + }
REMEDY${ + buildDetails.remedy + }
REGION${ + buildDetails.region + }
BUSINESS_UNIT${ + buildDetails.businessUnit + }
NOTIFICATION_ID${ + buildDetails.notification_id + }
+ + ${tableRows} +
`; + + let body = + "

Tombolo has detected an Orbit Build Monitoring Condition.



"; + + body = body + table; + + body = body + `

-Tombolo

`; + return body; + }, + + // TODO make this message card general by changing the key name + orbitMonitoringMessageCard: function (title, buildDetails, notification_id) { + let issue = ``; + + if ( + buildDetails.issue.metaDifference && + buildDetails.issue.metaDifference.length > 0 + ) { + buildDetails.issue.metaDifference.forEach((meta) => { + issue += `Issue: ${meta.attribute}
`; + issue += `Detected Value: ${meta.newValue}
`; + }); + } + + issue += `HOST: ${buildDetails.issue.host}
`; + issue += `JOB NAME: ${buildDetails.issue.build}
`; + issue += `MONITORING SCHEDULE: ${buildDetails.issue.cron}
`; + issue += `RETURNED JOB DATE: ${buildDetails.date}
`; + issue += `RETURNED JOB STATE: ${buildDetails.issue.status}
`; + issue += `RETURNED JOB WUID: ${buildDetails.issue.workUnit}
`; + + let tableRows = `PRODUCT${buildDetails.product.toUpperCase()} + ISSUE${issue} + SEV_CODE${ + buildDetails.severityCode + } + DATE_DETECTED${ + buildDetails.date + } + REMEDY${ + buildDetails.remedy + } + REGION${ + buildDetails.region + } + BUSINESS_UNIT${ + buildDetails.businessUnit + } + NOTIFICATION_ID${ + buildDetails.notification_id + }`; + + const table = `
+ + ${tableRows} +
`; + + let allFacts = []; + + allFacts.push({ name: "", value: table }); + cardData = `"notification_id": "${notification_id}"`; + + const body = JSON.stringify({ + "@type": "MessageCard", + "@context": "https://schema.org/extensions", + summary: title, + themeColor: "0072C6", + title: title, + sections: [ + { + facts: allFacts, + }, + { + type: "MessageCard", + contentType: "text/html", + text: " ", + }, + ], + + potentialAction: [ + { + "@type": "ActionCard", + name: "Add a comment", + inputs: [ + { + "@type": "TextInput", + id: "comment", + isMultiline: false, + title: "Add a comment here for this task", + }, + ], + actions: [ + { + "@type": "HttpPOST", + name: "Add comment", + target: process.env.API_URL + "/api/updateNotification/update", + body: `{"comment":"{{comment.value}}", ${cardData}}`, + isRequired: true, + errorMessage: "Comment cannot be blank", + }, + ], + }, + { + "@type": "ActionCard", + name: "Change status", + inputs: [ + { + "@type": "MultichoiceInput", + id: "list", + title: "Select a status", + isMultiSelect: "false", + choices: [ + { + display: "Triage", + value: "triage", + }, + { + display: "In Progress", + value: "inProgress", + }, + { + display: "Completed", + value: "completed", + }, + ], + }, + ], + actions: [ + { + "@type": "HttpPOST", + name: "Save", + target: process.env.API_URL + "/api/updateNotification/update", + body: `{"status":"{{list.value}}", ${cardData}}`, + isRequired: true, + errorMessage: "Select an option", + }, + ], + }, + ], + }); + + return body; + }, + //Orbit Build email body + + orbitBuildEmailBody: function (buildDetails) { + const tableRows = ` + ${buildDetails.name} + ${buildDetails.status} + ${buildDetails.subStatus} + ${buildDetails.lastrun} + ${buildDetails.HpccWorkUnit} + ${buildDetails.lastRun} + ${buildDetails.workunit} + `; + + const table = `
+ + ${tableRows} +
Name Status SubStatusLast Run Work Unit
`; + + let body = + "

Tombolo has detected an Orbit Build with a megaphone substatus.



"; + body = body + table; + body = body + `

-Tombolo

`; + + return body; + }, + + // TODO make this message card general by changing the key name + orbitBuildMessageCard: function (title, facts, notification_id) { + let allFacts = []; + cardData = `"notification_id": "${notification_id}"`; + facts.forEach((fact) => { + for (let key in fact) { + allFacts.push({ name: key, value: fact[key] }); + } + allFacts = [...allFacts]; + }); + const body = JSON.stringify({ + "@type": "MessageCard", + "@context": "https://schema.org/extensions", + summary: title, + themeColor: "0072C6", + title: title, + sections: [ + { + facts: allFacts, + }, + { + type: "MessageCard", + contentType: "text/html", + text: " ", + }, + ], + + potentialAction: [ + { + "@type": "ActionCard", + name: "Add a comment", + inputs: [ + { + "@type": "TextInput", + id: "comment", + isMultiline: false, + title: "Add a comment here for this task", + }, + ], + actions: [ + { + "@type": "HttpPOST", + name: "Add comment", + target: process.env.API_URL + "/api/updateNotification/update", + body: `{"comment":"{{comment.value}}", ${cardData}}`, + isRequired: true, + errorMessage: "Comment cannot be blank", + }, + ], + }, + { + "@type": "ActionCard", + name: "Change status", + inputs: [ + { + "@type": "MultichoiceInput", + id: "list", + title: "Select a status", + isMultiSelect: "false", + choices: [ + { + display: "Triage", + value: "triage", + }, + { + display: "In Progress", + value: "inProgress", + }, + { + display: "Completed", + value: "completed", + }, + ], + }, + ], + actions: [ + { + "@type": "HttpPOST", + name: "Save", + target: process.env.API_URL + "/api/updateNotification/update", + body: `{"status":"{{list.value}}", ${cardData}}`, + isRequired: true, + errorMessage: "Select an option", + }, + ], + }, + ], + }); + + return body; + }, + //Cluster monitoring email body clusterMonitoringEmailBody: function (facts) { let body = "
"; @@ -60,7 +368,7 @@ module.exports = { } allFacts = [...allFacts]; }); - const body = JSON.stringify({ + const body = { "@type": "MessageCard", "@context": "https://schema.org/extensions", summary: title, @@ -137,12 +445,11 @@ module.exports = { ], }, ], - }); + }; return body; }, - //Message card for landing zone messageCardBody: function ({ notificationDetails, diff --git a/server/jobs/orbitMegaphone.js b/server/jobs/orbitMegaphone.js new file mode 100644 index 000000000..19547cc4b --- /dev/null +++ b/server/jobs/orbitMegaphone.js @@ -0,0 +1,187 @@ +const logger = require("../config/logger"); +const sql = require("mssql"); +const models = require("../models"); +const integrations = models.integrations; +const orbitBuilds = models.orbitBuilds; +const monitoring_notifications = models.monitoring_notifications; +const notificationTemplate = require("./messageCards/notificationTemplate"); +const { notify } = require("../routes/notifications/email-notification"); +const { v4: uuidv4 } = require("uuid"); +const axios = require("axios"); + +const dbConfig = { + server: process.env.ORBIT_DB, + database: process.env.ORBIT_DB_NAME, + user: process.env.ORBIT_DB_USER, + password: process.env.ORBIT_DB_PWD, + port: parseInt(process.env.ORBIT_DB_PORT), + trustServerCertificate: true, +}; + +(async () => { + try { + //grab all orbit integrations that are active + const orbitIntegrations = await integrations.findAll({ + where: { + name: "Orbit", + active: true, + }, + }); + const sentNotifications = []; + + if (!orbitIntegrations.length) return; + + //if there are active integrations, grab new data and send notifications + orbitIntegrations.map(async (integration) => { + //if megaphone is not active, stop here and don't run + if (!integration.config.megaphoneActive) return; + + let application_id = integration.application_id; + + //connect to db + await sql.connect(dbConfig); + + const result = + await sql.query`select TOP 25 * from DimReceiveInstance where SubStatus_Code = 'MEGAPHONE' order by DateUpdated desc`; + + //just grab the rows from result + let rows = result?.recordset; + //loop through rows to build notifications and import + await Promise.all( + rows.map(async (build) => { + //check if the build already exists + let orbitBuild = await orbitBuilds.findOne({ + where: { + build_id: build.ReceiveInstanceIdKey, + application_id: application_id, + }, + raw: true, + }); + + //if it doesn't exist, create it and send a notification + if (!orbitBuild) { + //create build + const newBuild = await orbitBuilds.create({ + application_id: application_id, + build_id: build.ReceiveInstanceIdKey, + monitoring_id: null, + name: build.FileName, + type: "megaphone", + wuid: build.HpccWorkUnit, + metaData: { + lastRun: build.DateUpdated, + status: build.Status_Code, + subStatus: build.SubStatus_Code, + EnvironmentName: build.EnvironmentName, + initialStatus: build.Status_Code, + finalStatus: build.Status_Code, + }, + }); + + //if megaphone, send notification + // if (build.SubStatus_Code === "MEGAPHONE") + + //build and send email notification + if (integration.metaData.notificationEmails) { + let buildDetails = { + name: newBuild.name, + status: newBuild.metaData.status, + subStatus: newBuild.metaData.subStatus, + lastRun: newBuild.metaData.lastRun, + workunit: newBuild.metaData.workunit, + }; + + const emailBody = + notificationTemplate.orbitBuildEmailBody(buildDetails); + const emailRecipients = integration.metaData.notificationEmails; + + const notificationResponse = await notify({ + to: emailRecipients, + from: process.env.EMAIL_SENDER, + subject: + "Alert: Megaphone Substatus detected on Orbit Build " + + build.Name, + text: emailBody, + html: emailBody, + }); + + let notification_id = uuidv4(); + + sentNotifications.push({ + id: notification_id, + status: "notified", + notifiedTo: emailRecipients, + notification_channel: "eMail", + application_id, + notification_reason: "Megaphone Substatus", + monitoring_id: newBuild.id, + monitoring_type: "megaphone", + }); + } + + // //build and send Teams notification + if (integration.metaData.notificationWebhooks) { + let facts = [ + { name: newBuild.name }, + { Status: newBuild.metaData.status }, + { "Sub Status": newBuild.metaData.subStatus }, + { "Last Run": newBuild.metaData.lastRun }, + { WorkUnit: newBuild.metaData.workunit }, + ]; + let title = "Orbit Build Detectd With Megaphone Status"; + notification_id = uuidv4(); + const cardBody = notificationTemplate.orbitBuildMessageCard( + title, + facts, + notification_id + ); + + await axios.post( + integration.metaData.notificationWebhooks, + JSON.parse(cardBody) + ); + + sentNotifications.push({ + id: notification_id, + status: "notified", + notifiedTo: integration.metaData.notificationWebhooks, + notification_channel: "msTeams", + application_id, + notification_reason: "Megaphone Substatus", + monitoring_id: newBuild.id, + monitoring_type: "orbit", + }); + } + } else { + //if it does exist, update the "final status metadata" + + await orbitBuilds.update( + { + metaData: { + ...orbitBuild.metaData, + finalStatus: build.Status_Code, + }, + }, + { + where: { + build_id: build.ReceiveInstanceIdKey, + application_id: application_id, + }, + } + ); + } + + return true; + }) + ); + + // Record notifications + if (sentNotifications.length > 0) { + monitoring_notifications.bulkCreate(sentNotifications); + } + return; + }); + } catch (error) { + logger.error("Error while running Orbit Jobs: " + error); + } +})(); diff --git a/server/jobs/submitOrbitMonitoring.js b/server/jobs/submitOrbitMonitoring.js new file mode 100644 index 000000000..8aa25e3ce --- /dev/null +++ b/server/jobs/submitOrbitMonitoring.js @@ -0,0 +1,363 @@ +const axios = require("axios"); +const { notify } = require("../routes/notifications/email-notification"); +const { parentPort, workerData } = require("worker_threads"); +const logger = require("../config/logger"); +const models = require("../models"); +const orbitMonitoring = models.orbitMonitoring; +const orbitBuilds = models.orbitBuilds; +const { v4: uuidv4 } = require("uuid"); +const monitoring_notifications = models.monitoring_notifications; +const { + orbitMonitoringEmailBody, + orbitMonitoringMessageCard, +} = require("./messageCards/notificationTemplate"); +const { update } = require("lodash"); + +const sql = require("mssql"); + +const dbConfig = { + server: process.env.ORBIT_DB, + database: process.env.ORBIT_DB_NAME, + user: process.env.ORBIT_DB_USER, + password: process.env.ORBIT_DB_PWD, + port: parseInt(process.env.ORBIT_DB_PORT), + trustServerCertificate: true, +}; + +const runSQLQuery = async (query) => { + try { + await sql.connect(dbConfig); + + const result = await sql.query(query); + + return result; + } catch (err) { + console.log(err); + return { + err, + message: "There was an issue contacting the orbit reports server", + }; + } +}; + +(async () => { + try { + const orbitMonitoringDetails = await orbitMonitoring.findOne({ + where: { id: workerData.orbitMonitoring_id }, + raw: true, + }); + + const { + id, + name, + cron, + build, + isActive, + severityCode, + product, + businessUnit, + host, + primaryContact, + secondaryContact, + application_id, + metaData: { + lastWorkUnit, + notifications, + monitoringCondition, + monitoringCondition: { notifyCondition }, + }, + } = orbitMonitoringDetails; + + //get most recent WorkUnits + + const query = `select Top 10 HpccWorkUnit as 'WorkUnit', Name as 'Build', DateUpdated as 'Date', Status_Code as 'Status', Version, BuildTemplateIdKey as 'BuildID' from DimBuildInstance where Name = '${build}' order by Date desc`; + + const wuResult = await runSQLQuery(query); + + if (wuResult.err) { + throw Error(result.message); + } + + // Keep track of changes + const metaDifference = []; + + if (!wuResult || !wuResult.recordset || wuResult.recordset[0]) return; + + //check for most recent workunit changes and push to metadifference + if (notifyCondition.includes("buildStatus")) { + let newStatus = wuResult.recordset[0].Status; + newStatus = newStatus.toLowerCase(); + + if (monitoringCondition?.buildStatus.includes(newStatus)) { + //build status has been detected, push difference. + metaDifference.push({ + attribute: "Build Status", + oldValue: `${orbitMonitoringDetails.metaData.lastWorkUnit.lastWorkUnitStatus}`, + newValue: `${newStatus}`, + }); + } + } + + if ( + notifyCondition.includes("updateInterval") || + notifyCondition.includes("updateIntervalDays") + ) { + //update interval is in days, so multiply by 86400000 to get number of milliseconds between updates + let updateInterval = monitoringCondition?.updateInterval; + let updateIntervalDays = monitoringCondition?.updateIntervalDays; + + // let orbit = await hpccUtil.getorbit(clusterid, Name); + + // let newModified = orbit.modified; + + //dates to check update interval + const lastDate = new Date(modified); + const newDate = new Date(newModified); + const currentDate = new Date(); + + //get integer difference of days + const diffInMilliseconds = Math.abs(newDate - lastDate); + const diffDays = Math.ceil(diffInMilliseconds / (1000 * 60 * 60 * 24)); + + //get integer difference of days to current date + const diffInMilliCurrent = Math.abs(currentDate - lastDate); + const diffDaysCurrent = Math.ceil( + diffInMilliCurrent / (1000 * 60 * 60 * 24) + ); + + //if difference in days !== update interval, and the Orbit Build has been updated, notify + if (diffDays !== updateInterval && diffDays !== 0) { + metaDifference.push({ + attribute: "Orbit Build did not follow update schedule", + oldValue: `${updateInterval} - days defined between updates`, + newValue: `${diffDays} - days between last updates`, + }); + } + + //if current amount of days is > defined + if (diffDaysCurrent > updateInterval) { + metaDifference.push({ + attribute: "Orbit Build is overdue for update", + oldValue: `${updateInterval} - days defined between updates`, + newValue: `${diffDaysCurrent} - days since last update`, + }); + } + //if updateIntervalDays is set, check that most recent modified day of the week matches setting + if (updateIntervalDays?.length) { + const daysOfWeek = [ + "sunday", + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + ]; + const newDate = new Date(newModified); + const newDayUpdated = daysOfWeek[newDate.getDay()]; + + if (!updateIntervalDays.includes(newDayUpdated)) { + metaDifference.push({ + attribute: + "Orbit Build was updated on a day of the week not defined", + oldValue: `${updateIntervalDays} - days defined`, + newValue: `${newDayUpdated} - day updated`, + }); + } + } + + // update orbit monitoring last monitored date + const date = new Date(); + const currentTimeStamp = date.getTime(); + metaData = orbitMonitoringDetails.metaData; + metaData.lastMonitored = currentTimeStamp; + + await orbitMonitoring.update({ metaData }, { where: { id } }); + } + + //------------------------------------------------------------------------------ + // put result into orbitbuilds table + //check for status changes of the all gathered workunits + + await Promise.all( + wuResult.recordset.map(async (result) => { + //check if result is is orbitbuilds table + const orbitBuild = await orbitBuilds.findOne({ + where: { + wuid: result.WorkUnit, + monitoring_id: id, + application_id: application_id, + }, + raw: true, + }); + + if (orbitBuild) { + //if it does, update it + await orbitBuilds.update( + { + metaData: { + ...orbitBuild.metaData, + finalStatus: result.Status, + }, + }, + { where: { id: orbitBuild.id } } + ); + } else { + console.log("new build found, creating it!!" + result); + //if it doesn't, create it + await orbitBuilds.create({ + application_id: application_id, + build_id: result.BuildID, + monitoring_id: id, + name: result.Build, + type: "orbit", + wuid: result.WorkUnit, + metaData: { + lastRun: result.Date, + status: result.Status, + workunit: result.WorkUnit, + initialStatus: result.Status, + finalStatus: result.Status, + version: result.Version, + }, + }); + } + return true; + }) + ); + + //------------------------------------------------------------------------------ + + // Check what notification channel is set up + let emailNotificationDetails; + let teamsNotificationDetails; + + // notifications.channel === "eMail" + + for (let notification of notifications) { + if (notification.channel === "eMail") { + emailNotificationDetails = notification; + } + if (notification.channel === "msTeams") { + teamsNotificationDetails = notification; + } + } + + const sentNotifications = []; + + let notificationDetails = {}; + + if (metaDifference.length > 0) { + // Note - this does not cover Orbit Build size not in range + notificationDetails.value = + "Orbit Build alert has been triggered by Tombolo"; + notificationDetails.title = `Orbit Build alert has been triggered by Tombolo`; + notificationDetails.text = `Orbit Build alert has been triggered by Tombolo`; + } + + const notification_id = uuidv4(); + + //build out buildDetails for notification + + const buildDetails = { + title: notificationDetails.title, + product: product, + businessUnit: businessUnit, + region: "USA", + severityCode: severityCode, + date: wuResult.recordset[0].Date, + remedy: + "Please Contact one of the following for assistance
" + + "PRIMARY: " + + primaryContact + + "
" + + "SECONDARY: " + + secondaryContact, + notification_id: notification_id, + issue: { + metaDifference: metaDifference, + status: wuResult.recordset[0].Status, + build: build, + host: host, + workUnit: wuResult.recordset[0].WorkUnit, + cron: cron, + }, + }; + + // E-mail notification + if (emailNotificationDetails && notificationDetails.text) { + try { + const body = orbitMonitoringEmailBody(buildDetails); + const notificationResponse = await notify({ + to: emailNotificationDetails.recipients, + from: process.env.EMAIL_SENDER, + subject: notificationDetails.title, + text: body, + html: body, + }); + + if (notificationResponse.accepted) { + sentNotifications.push({ + id: notification_id, + file_name: build, + monitoring_type: "orbitMonitoring", + status: "notified", + notifiedTo: emailNotificationDetails.recipients, + notification_channel: "eMail", + application_id, + notification_reason: notificationDetails.value, + monitoring_id: id, + }); + } + } catch (err) { + logger.error(err); + } + } + + // Teams notification + if (teamsNotificationDetails && notificationDetails.text) { + const { recipients } = teamsNotificationDetails; + + for (let recipient of recipients) { + let title = "Orbit Monitoring alert has been triggered by Tombolo"; + + try { + let body = orbitMonitoringMessageCard( + title, + buildDetails, + notification_id + ); + + await axios.post(recipient, JSON.parse(body)); + + sentNotifications.push({ + id: notification_id, + file_name: build, + monitoring_type: "orbitMonitoring", + status: "notified", + notifiedTo: teamsNotificationDetails.recipients, + notification_channel: "msTeams", + application_id, + notification_reason: notificationDetails.value, + monitoring_id: id, + }); + } catch (err) { + logger.error(err); + } + } + } + + // Add sent notifications to notification table + if (sentNotifications.length > 0) { + try { + await monitoring_notifications.bulkCreate(sentNotifications); + } catch (err) { + logger.error(err); + } + } + } catch (err) { + logger.error(err); + } finally { + parentPort ? parentPort.postMessage("done") : process.exit(0); + } +})(); diff --git a/server/migrations/20230629184052-add_metaData_to_monitoring_notification_table.js b/server/migrations/20230629184052-add_metaData_to_monitoring_notification_table.js index 17bdfef2b..d4901ed66 100644 --- a/server/migrations/20230629184052-add_metaData_to_monitoring_notification_table.js +++ b/server/migrations/20230629184052-add_metaData_to_monitoring_notification_table.js @@ -33,4 +33,4 @@ module.exports = { throw err; } }, -}; +}; diff --git a/server/migrations/20230906000000-create-OrbitMonitoring.js b/server/migrations/20230906000000-create-OrbitMonitoring.js new file mode 100644 index 000000000..63bd8be89 --- /dev/null +++ b/server/migrations/20230906000000-create-OrbitMonitoring.js @@ -0,0 +1,89 @@ +"use strict"; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable("orbitMonitoring", { + id: { + allowNull: false, + primaryKey: true, + type: Sequelize.UUID, + defaultValue: Sequelize.UUIDV4, + }, + application_id: { + type: Sequelize.UUID, + references: { + model: "application", + key: "id", + }, + onUpdate: "CASCADE", + onDelete: "CASCADE", + }, + name: { + allowNull: false, + type: Sequelize.DataTypes.STRING, + }, + cron: { + allowNull: false, + type: Sequelize.DataTypes.STRING, + }, + isActive: { + allowNull: false, + type: Sequelize.DataTypes.BOOLEAN, + defaultValue: false, + }, + build: { + allowNull: false, + type: Sequelize.DataTypes.STRING, + defaultValue: false, + }, + severityCode: { + allowNull: false, + type: Sequelize.DataTypes.TINYINT, + defaultValue: 0, + }, + businessUnit: { + allowNull: false, + type: Sequelize.DataTypes.STRING, + defaultValue: false, + }, + product: { + allowNull: false, + type: Sequelize.DataTypes.STRING, + defaultValue: false, + }, + host: { + allowNull: false, + type: Sequelize.DataTypes.STRING, + defaultValue: false, + }, + primaryContact: { + allowNull: true, + type: Sequelize.DataTypes.STRING, + defaultValue: false, + }, + secondaryContact: { + allowNull: true, + type: Sequelize.DataTypes.STRING, + defaultValue: false, + }, + metaData: { + type: Sequelize.JSON, + allowNull: true, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + deletedAt: { + allowNull: true, + type: Sequelize.DATE, + }, + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable("orbitMonitoring"); + }, +}; diff --git a/server/migrations/20230906195957-create-orbit-builds.js b/server/migrations/20230906195957-create-orbit-builds.js new file mode 100644 index 000000000..50b065016 --- /dev/null +++ b/server/migrations/20230906195957-create-orbit-builds.js @@ -0,0 +1,66 @@ +"use strict"; +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.createTable("orbitBuilds", { + id: { + allowNull: false, + primaryKey: true, + type: Sequelize.UUID, + defaultValue: Sequelize.UUIDV4, + }, + application_id: { + type: Sequelize.UUID, + references: { + model: "application", + key: "id", + }, + onUpdate: "CASCADE", + onDelete: "CASCADE", + }, + monitoring_id: { + type: Sequelize.UUID, + references: { + model: "orbitMonitoring", + key: "id", + }, + onUpdate: "CASCADE", + onDelete: "CASCADE", + }, + type: { + type: Sequelize.STRING, + allowNull: false, + }, + build_id: { + type: Sequelize.STRING, + allowNull: false, + }, + wuid: { + type: Sequelize.STRING, + allowNull: false, + }, + name: { + allowNull: false, + type: Sequelize.DataTypes.STRING, + }, + metaData: { + type: Sequelize.JSON, + allowNull: true, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + deletedAt: { + allowNull: true, + type: Sequelize.DATE, + }, + }); + }, + async down(queryInterface, Sequelize) { + await queryInterface.dropTable("orbitBuilds"); + }, +}; diff --git a/server/migrations/20230907145012-create-integrations.js b/server/migrations/20230907145012-create-integrations.js new file mode 100644 index 000000000..40f14238e --- /dev/null +++ b/server/migrations/20230907145012-create-integrations.js @@ -0,0 +1,43 @@ +"use strict"; +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.createTable("integrations", { + id: { + allowNull: false, + primaryKey: true, + type: Sequelize.UUID, + defaultValue: Sequelize.UUIDV4, + }, + application_id: { + type: Sequelize.UUID, + }, + name: { + type: Sequelize.STRING, + }, + description: { + type: Sequelize.STRING, + }, + active: { + type: Sequelize.BOOLEAN, + }, + config: { + type: Sequelize.JSON, + }, + metaData: { + type: Sequelize.JSON, + allowNull: true, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, + async down(queryInterface, Sequelize) { + await queryInterface.dropTable("integrations"); + }, +}; diff --git a/server/models/integrations.js b/server/models/integrations.js new file mode 100644 index 000000000..ba3e5ddc5 --- /dev/null +++ b/server/models/integrations.js @@ -0,0 +1,53 @@ +"use strict"; +module.exports = (sequelize, DataTypes) => { + const integrations = sequelize.define( + "integrations", + { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + allowNull: false, + autoIncrement: false, + }, + name: { + allowNull: false, + type: DataTypes.STRING, + unique: false, + }, + description: { + allowNull: false, + type: DataTypes.STRING, + unique: false, + }, + active: { + allowNull: false, + type: DataTypes.BOOLEAN, + }, + config: { + type: DataTypes.JSON, + allowNull: true, + }, + metaData: { + type: DataTypes.JSON, + allowNull: true, + }, + application_id: { + allowNull: false, + type: DataTypes.UUID, + }, + }, + { freezeTableName: true } + ); + integrations.associate = function (models) { + // Define association here + integrations.belongsTo(models.application, { + foreignKey: "application_id", + }); + integrations.hasMany(models.monitoring_notifications, { + foreignKey: "application_id", + onDelete: "CASCADE", + }); + }; + return integrations; +}; diff --git a/server/models/orbitMonitoring.js b/server/models/orbitMonitoring.js new file mode 100644 index 000000000..f1ccaa020 --- /dev/null +++ b/server/models/orbitMonitoring.js @@ -0,0 +1,76 @@ +"use strict"; +module.exports = (sequelize, DataTypes) => { + const orbitMonitoring = sequelize.define( + "orbitMonitoring", + { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + allowNull: false, + autoIncrement: false, + }, + application_id: { + allowNull: false, + type: DataTypes.UUID, + }, + name: { + allowNull: false, + type: DataTypes.STRING, + unique: true, + }, + cron: { + allowNull: true, + type: DataTypes.STRING, + }, + build: { + allowNull: false, + type: DataTypes.STRING, + }, + businessUnit: { + allowNull: false, + type: DataTypes.STRING, + }, + product: { + allowNull: false, + type: DataTypes.STRING, + }, + severityCode: { + allowNull: false, + type: DataTypes.TINYINT, + }, + host: { + allowNull: false, + type: DataTypes.STRING, + }, + primaryContact: { + allowNull: true, + type: DataTypes.STRING, + }, + secondaryContact: { + allowNull: true, + type: DataTypes.STRING, + }, + metaData: { + type: DataTypes.JSON, + allowNull: true, + }, + isActive: { + type: DataTypes.BOOLEAN, + allowNull: true, + }, + }, + { paranoid: true, freezeTableName: true } + ); + orbitMonitoring.associate = function (models) { + // Define association here + orbitMonitoring.belongsTo(models.application, { + foreignKey: "application_id", + }); + orbitMonitoring.hasMany(models.monitoring_notifications, { + foreignKey: "application_id", + onDelete: "CASCADE", + }); + }; + return orbitMonitoring; +}; diff --git a/server/models/orbitbuilds.js b/server/models/orbitbuilds.js new file mode 100644 index 000000000..c38d676c3 --- /dev/null +++ b/server/models/orbitbuilds.js @@ -0,0 +1,60 @@ +"use strict"; +module.exports = (sequelize, DataTypes) => { + const orbitBuilds = sequelize.define( + "orbitBuilds", + { + id: { + primaryKey: true, + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + allowNull: false, + autoIncrement: false, + }, + name: { + allowNull: false, + type: DataTypes.STRING, + unique: true, + }, + application_id: { + allowNull: false, + type: DataTypes.UUID, + }, + monitoring_id: { + allowNull: true, + type: DataTypes.UUID, + }, + type: { + type: DataTypes.STRING, + allowNull: false, + }, + wuid: { + type: DataTypes.STRING, + allowNull: false, + }, + metaData: { + type: DataTypes.JSON, + allowNull: true, + }, + build_id: { + type: DataTypes.STRING, + allowNull: false, + }, + metaData: { + type: DataTypes.JSON, + allowNull: true, + }, + }, + { paranoid: true, freezeTableName: true } + ); + orbitBuilds.associate = function (models) { + // Define association here + orbitBuilds.belongsTo(models.application, { + foreignKey: "application_id", + }); + orbitBuilds.hasMany(models.monitoring_notifications, { + foreignKey: "application_id", + onDelete: "CASCADE", + }); + }; + return orbitBuilds; +}; diff --git a/server/package-lock.json b/server/package-lock.json index d0cc6b202..6e718e1a7 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -25,6 +25,7 @@ "lodash": "^4.17.20", "moment": "^2.29.4", "morgan": "^1.10.0", + "mssql": "^9.1.3", "multer": "^1.4.5-lts.1", "mysql2": "^1.7.0", "nodemailer": "^6.7.1", @@ -39,6 +40,7 @@ "sequelize-cli": "^6.4.1", "simple-git": "^3.6.0", "socket.io": "^4.6.1", + "sqlstring": "^2.3.3", "tmp": "^0.1.0", "uuid": "^3.4.0", "winston": "^3.7.2" @@ -62,6 +64,281 @@ "node": ">=6.0.0" } }, + "node_modules/@azure/abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", + "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.5.0.tgz", + "integrity": "sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-util": "^1.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.7.3.tgz", + "integrity": "sha512-kleJ1iUTxcO32Y06dH9Pfi9K4U+Tlb111WXEnbt7R/ne+NLRwppZiTGJuTD5VVoxTMK5NTbEtm5t2vcdNCFe2g==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-http-compat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-1.3.0.tgz", + "integrity": "sha512-ZN9avruqbQ5TxopzG3ih3KRy52n8OAbitX3fnZT5go4hzu0J+KVPSzkL+Wt3hpJpdG8WIfg1sBD1tWkgUdEpBA==", + "dependencies": { + "@azure/abort-controller": "^1.0.4", + "@azure/core-client": "^1.3.0", + "@azure/core-rest-pipeline": "^1.3.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-lro": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.4.tgz", + "integrity": "sha512-3GJiMVH7/10bulzOKGrrLeG/uCBH/9VtxqaMcB9lIqAeamI/xYQSHJL/KcsLDuH+yTjYpro/u6D/MuRe4dN70Q==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-util": "^1.2.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-paging": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.5.0.tgz", + "integrity": "sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.12.0.tgz", + "integrity": "sha512-+MnSB0vGZjszSzr5AW8z93/9fkDu2RLtWmAN8gskURq7EW2sSwqy8jZa0V26rjuBVkwhdA3Hw8z3VWoeBUOw+A==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.3.0", + "@azure/logger": "^1.0.0", + "form-data": "^4.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", + "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.4.0.tgz", + "integrity": "sha512-eGAyJpm3skVQoLiRqm/xPa+SXi/NPDdSHMxbRAz2lSprd+Zs+qrpQGQQ2VQ3Nttu+nSZR4XoYQC71LbEI7jsig==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-2.1.0.tgz", + "integrity": "sha512-BPDz1sK7Ul9t0l9YKLEa8PHqWU4iCfhGJ+ELJl6c8CP3TpJt2urNCbm0ZHsthmxRsYoMPbz2Dvzj30zXZVmAFw==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.4.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^2.26.0", + "@azure/msal-common": "^7.0.0", + "@azure/msal-node": "^1.10.0", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", + "tslib": "^2.2.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/identity/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@azure/identity/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@azure/identity/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@azure/keyvault-keys": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.7.1.tgz", + "integrity": "sha512-zfmlZQCw1Yz+aPhgZmWOYBUzaKmfBzR2yceAE4S6hKDl7YZraTguuXmtFbCqjRvpz+pIMKAK25fENay9mFy1hQ==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.5.0", + "@azure/core-http-compat": "^1.3.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.1.1", + "@azure/core-rest-pipeline": "^1.8.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz", + "integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "2.38.1", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.38.1.tgz", + "integrity": "sha512-NROo7mLpw7aWtj8tWy9ZPz3WWiudwVAOIDZ1K3PPrjDAA4kFYayWlbZiJl1T1sD5Oqwa6FOtwzFSvuUj1CWp6Q==", + "dependencies": { + "@azure/msal-common": "13.2.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-browser/node_modules/@azure/msal-common": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.2.1.tgz", + "integrity": "sha512-9CtyVdDtAOw+raemKg8gdBuE7gleObgSb7p4bzMIlUt8eM69/Gaow7uqr1gK3jLYINSrss32OZW8mBbdgVLiHg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-7.6.0.tgz", + "integrity": "sha512-XqfbglUTVLdkHQ8F9UQJtKseRr3sSnr9ysboxtoswvaMVaEfvyLtMoHv9XdKUfOc0qKGzNgRFd9yRjIWVepl6Q==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.18.1.tgz", + "integrity": "sha512-B4kUOWJoN4vD8b3pGJ9Q9mIZhaDb8EnQM1aN0x1otlQgTfzDvEk6rWc6fy8uGdtXqcNddBtiXdc4oRiItroVkA==", + "dependencies": { + "@azure/msal-common": "13.2.1", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": "10 || 12 || 14 || 16 || 18" + } + }, + "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.2.1.tgz", + "integrity": "sha512-9CtyVdDtAOw+raemKg8gdBuE7gleObgSb7p4bzMIlUt8eM69/Gaow7uqr1gK3jLYINSrss32OZW8mBbdgVLiHg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -1180,6 +1457,11 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, + "node_modules/@js-joda/core": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.5.3.tgz", + "integrity": "sha512-7dqNYwG8gCt4hfg5PKgM7xLEcgSBcx/UgC92OMnhMmvAnq11QzDFPrxUkNR/u5kn17WWLZ8beZ4A3Qrz4pZcmQ==" + }, "node_modules/@kwsites/file-exists": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", @@ -1222,6 +1504,19 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" }, + "node_modules/@tediousjs/connection-string": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-0.4.4.tgz", + "integrity": "sha512-Qssn7gmOILmqD0zkfA09YyFd52UajWYkLTTSi4Dx/XZaUuVcx4W4guv2rAVc5mm8wYRdonmG/HfFH3PS6izXAg==" + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, "node_modules/@types/babel__core": { "version": "7.20.0", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", @@ -1509,11 +1804,42 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", + "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -1554,6 +1880,17 @@ "node": ">= 4.0.0" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -1765,6 +2102,29 @@ "node": ">=8" } }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -1986,7 +2346,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -2478,6 +2837,29 @@ "node": ">=0.10.0" } }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2698,6 +3080,107 @@ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "node_modules/es-abstract": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", + "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.1", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.1", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "safe-array-concat": "^1.0.0", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-aggregate-error": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.9.tgz", + "integrity": "sha512-fvnX40sb538wdU6r4s35cq4EY6Lr09Upj40BEVem4LEsuW8XgQep9yD5Q1U2KftokNp1rWODFJ2qwZSsAjFpbg==", + "dependencies": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "function-bind": "^1.1.1", + "functions-have-names": "^1.2.3", + "get-intrinsic": "^1.1.3", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es5-ext": { "version": "0.10.53", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", @@ -2790,6 +3273,14 @@ "node": ">=6" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -3062,6 +3553,14 @@ } } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -3167,6 +3666,31 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -3196,7 +3720,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -3228,13 +3751,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", "dependencies": { - "assert-plus": "^1.0.0" - } + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": "^1.0.0" + } }, "node_modules/glob": { "version": "6.0.4", @@ -3294,6 +3832,31 @@ "node": ">=4" } }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.9", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", @@ -3331,6 +3894,14 @@ "node": ">= 0.4.0" } }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -3340,11 +3911,21 @@ "node": ">=4" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -3356,7 +3937,20 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, "engines": { "node": ">= 0.4" }, @@ -3394,6 +3988,19 @@ "node": ">= 0.6" } }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -3528,6 +4135,19 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -3536,11 +4156,35 @@ "node": ">= 0.10" } }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3553,6 +4197,32 @@ "node": ">=8" } }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", @@ -3564,6 +4234,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", @@ -3611,6 +4309,17 @@ "node": ">=0.10.0" } }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3620,6 +4329,20 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", @@ -3630,6 +4353,32 @@ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -3641,6 +4390,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-string-and-not-blank": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/is-string-and-not-blank/-/is-string-and-not-blank-0.0.2.tgz", @@ -3657,6 +4420,34 @@ "resolved": "https://registry.npmjs.org/is-string-blank/-/is-string-blank-1.0.1.tgz", "integrity": "sha512-9H+ZBCVs3L9OYqv8nuUAzpcT9OTgMD1yAWrG7ihlnibdkbtB850heAmYWxHuXc4CHy4lKeK69tN+ny1K7gBIrw==" }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -3673,6 +4464,28 @@ "node": ">=0.10.0" } }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -4471,6 +5284,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4503,6 +5321,11 @@ "node": ">=4" } }, + "node_modules/jsbi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz", + "integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==" + }, "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -5030,6 +5853,33 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/mssql": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/mssql/-/mssql-9.1.3.tgz", + "integrity": "sha512-oXs2lJ1vKUe2s0twCdcdKnqATTVaIswzpSiGnUjMIhV6Sip9vEDuYt3dCoVWXXNuPJ5iFIqLxvagw4Hrz6xR4A==", + "dependencies": { + "@tediousjs/connection-string": "^0.4.1", + "commander": "^11.0.0", + "debug": "^4.3.3", + "rfdc": "^1.3.0", + "tarn": "^3.0.2", + "tedious": "^15.0.1" + }, + "bin": { + "mssql": "bin/mssql" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mssql/node_modules/commander": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", + "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", + "engines": { + "node": ">=16" + } + }, "node_modules/multer": { "version": "1.4.5-lts.1", "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", @@ -5116,9 +5966,9 @@ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "node_modules/nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", "optional": true }, "node_modules/nanoclone": { @@ -5126,6 +5976,11 @@ "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" }, + "node_modules/native-duplexpair": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", + "integrity": "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA==" + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5154,6 +6009,11 @@ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -5341,7 +6201,31 @@ "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5396,6 +6280,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -5850,6 +6750,22 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -5980,6 +6896,11 @@ "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.4.tgz", "integrity": "sha512-XgmCoxKWkDofwH8WddD0w85ZfqYz+ZHlr5yo+3YUCfycWawU56T5ckWXsScsj5B8tqUcIG67DxXByo3VUgiAdA==" }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, "node_modules/rimraf": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", @@ -5992,6 +6913,28 @@ "rimraf": "bin.js" } }, + "node_modules/safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -6003,6 +6946,19 @@ "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", "optional": true }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-stable-stringify": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", @@ -6242,7 +7198,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -6388,9 +7343,9 @@ "dev": true }, "node_modules/sqlstring": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", - "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", "engines": { "node": ">= 0.6" } @@ -6455,6 +7410,15 @@ "node": ">=0.10.0" } }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -6497,6 +7461,48 @@ "node": ">=8" } }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -6636,6 +7642,52 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tarn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", + "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/tedious": { + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/tedious/-/tedious-15.1.3.tgz", + "integrity": "sha512-166EpRm5qknwhEisjZqz/mF7k14fXKJYHRg6XiAXVovd/YkyHJ3SG4Ppy89caPaNFfRr7PVYe+s4dAvKaCMFvw==", + "dependencies": { + "@azure/identity": "^2.0.4", + "@azure/keyvault-keys": "^4.4.0", + "@js-joda/core": "^5.2.0", + "bl": "^5.0.0", + "es-aggregate-error": "^1.0.8", + "iconv-lite": "^0.6.3", + "js-md4": "^0.3.2", + "jsbi": "^4.3.0", + "native-duplexpair": "^1.0.0", + "node-abort-controller": "^3.0.1", + "punycode": "^2.1.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/tedious/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tedious/node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -6886,6 +7938,67 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -6902,6 +8015,20 @@ "node": ">=6.0.0" } }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -7133,6 +8260,39 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/winston": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/winston/-/winston-3.7.2.tgz", @@ -7395,6 +8555,229 @@ "@jridgewell/trace-mapping": "^0.3.9" } }, + "@azure/abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", + "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/core-auth": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.5.0.tgz", + "integrity": "sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-util": "^1.1.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-client": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.7.3.tgz", + "integrity": "sha512-kleJ1iUTxcO32Y06dH9Pfi9K4U+Tlb111WXEnbt7R/ne+NLRwppZiTGJuTD5VVoxTMK5NTbEtm5t2vcdNCFe2g==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-http-compat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-1.3.0.tgz", + "integrity": "sha512-ZN9avruqbQ5TxopzG3ih3KRy52n8OAbitX3fnZT5go4hzu0J+KVPSzkL+Wt3hpJpdG8WIfg1sBD1tWkgUdEpBA==", + "requires": { + "@azure/abort-controller": "^1.0.4", + "@azure/core-client": "^1.3.0", + "@azure/core-rest-pipeline": "^1.3.0" + } + }, + "@azure/core-lro": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.4.tgz", + "integrity": "sha512-3GJiMVH7/10bulzOKGrrLeG/uCBH/9VtxqaMcB9lIqAeamI/xYQSHJL/KcsLDuH+yTjYpro/u6D/MuRe4dN70Q==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-util": "^1.2.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-paging": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.5.0.tgz", + "integrity": "sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/core-rest-pipeline": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.12.0.tgz", + "integrity": "sha512-+MnSB0vGZjszSzr5AW8z93/9fkDu2RLtWmAN8gskURq7EW2sSwqy8jZa0V26rjuBVkwhdA3Hw8z3VWoeBUOw+A==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.3.0", + "@azure/logger": "^1.0.0", + "form-data": "^4.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "tslib": "^2.2.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, + "@azure/core-tracing": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", + "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/core-util": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.4.0.tgz", + "integrity": "sha512-eGAyJpm3skVQoLiRqm/xPa+SXi/NPDdSHMxbRAz2lSprd+Zs+qrpQGQQ2VQ3Nttu+nSZR4XoYQC71LbEI7jsig==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/identity": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-2.1.0.tgz", + "integrity": "sha512-BPDz1sK7Ul9t0l9YKLEa8PHqWU4iCfhGJ+ELJl6c8CP3TpJt2urNCbm0ZHsthmxRsYoMPbz2Dvzj30zXZVmAFw==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.4.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^2.26.0", + "@azure/msal-common": "^7.0.0", + "@azure/msal-node": "^1.10.0", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", + "tslib": "^2.2.0", + "uuid": "^8.3.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } + } + }, + "@azure/keyvault-keys": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.7.1.tgz", + "integrity": "sha512-zfmlZQCw1Yz+aPhgZmWOYBUzaKmfBzR2yceAE4S6hKDl7YZraTguuXmtFbCqjRvpz+pIMKAK25fENay9mFy1hQ==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.5.0", + "@azure/core-http-compat": "^1.3.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.1.1", + "@azure/core-rest-pipeline": "^1.8.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/logger": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz", + "integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/msal-browser": { + "version": "2.38.1", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.38.1.tgz", + "integrity": "sha512-NROo7mLpw7aWtj8tWy9ZPz3WWiudwVAOIDZ1K3PPrjDAA4kFYayWlbZiJl1T1sD5Oqwa6FOtwzFSvuUj1CWp6Q==", + "requires": { + "@azure/msal-common": "13.2.1" + }, + "dependencies": { + "@azure/msal-common": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.2.1.tgz", + "integrity": "sha512-9CtyVdDtAOw+raemKg8gdBuE7gleObgSb7p4bzMIlUt8eM69/Gaow7uqr1gK3jLYINSrss32OZW8mBbdgVLiHg==" + } + } + }, + "@azure/msal-common": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-7.6.0.tgz", + "integrity": "sha512-XqfbglUTVLdkHQ8F9UQJtKseRr3sSnr9ysboxtoswvaMVaEfvyLtMoHv9XdKUfOc0qKGzNgRFd9yRjIWVepl6Q==" + }, + "@azure/msal-node": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.18.1.tgz", + "integrity": "sha512-B4kUOWJoN4vD8b3pGJ9Q9mIZhaDb8EnQM1aN0x1otlQgTfzDvEk6rWc6fy8uGdtXqcNddBtiXdc4oRiItroVkA==", + "requires": { + "@azure/msal-common": "13.2.1", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "dependencies": { + "@azure/msal-common": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.2.1.tgz", + "integrity": "sha512-9CtyVdDtAOw+raemKg8gdBuE7gleObgSb7p4bzMIlUt8eM69/Gaow7uqr1gK3jLYINSrss32OZW8mBbdgVLiHg==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } + } + }, "@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -8255,6 +9638,11 @@ } } }, + "@js-joda/core": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.5.3.tgz", + "integrity": "sha512-7dqNYwG8gCt4hfg5PKgM7xLEcgSBcx/UgC92OMnhMmvAnq11QzDFPrxUkNR/u5kn17WWLZ8beZ4A3Qrz4pZcmQ==" + }, "@kwsites/file-exists": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", @@ -8297,6 +9685,16 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" }, + "@tediousjs/connection-string": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-0.4.4.tgz", + "integrity": "sha512-Qssn7gmOILmqD0zkfA09YyFd52UajWYkLTTSi4Dx/XZaUuVcx4W4guv2rAVc5mm8wYRdonmG/HfFH3PS6izXAg==" + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" + }, "@types/babel__core": { "version": "7.20.0", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", @@ -8547,11 +9945,33 @@ "sprintf-js": "~1.0.2" } }, + "array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "requires": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + } + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "arraybuffer.prototype.slice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", + "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "requires": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + } + }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -8586,6 +10006,11 @@ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -8743,6 +10168,28 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "requires": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -8914,7 +10361,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -9292,6 +10738,20 @@ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" + }, + "define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -9473,6 +10933,86 @@ } } }, + "es-abstract": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", + "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", + "requires": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.1", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.1", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "safe-array-concat": "^1.0.0", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.10" + } + }, + "es-aggregate-error": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.9.tgz", + "integrity": "sha512-fvnX40sb538wdU6r4s35cq4EY6Lr09Upj40BEVem4LEsuW8XgQep9yD5Q1U2KftokNp1rWODFJ2qwZSsAjFpbg==", + "requires": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "function-bind": "^1.1.1", + "functions-have-names": "^1.2.3", + "get-intrinsic": "^1.1.3", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.0" + } + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "es5-ext": { "version": "0.10.53", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", @@ -9553,6 +11093,11 @@ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, "execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -9773,6 +11318,14 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==" }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -9849,6 +11402,22 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, "generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -9872,7 +11441,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -9892,6 +11460,15 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -9945,6 +11522,22 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "requires": { + "define-properties": "^1.1.3" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "graceful-fs": { "version": "4.2.9", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", @@ -9972,23 +11565,42 @@ "function-bind": "^1.1.1" } }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } + }, "has-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } }, "hexoid": { "version": "1.0.0", @@ -10014,6 +11626,16 @@ "toidentifier": "1.0.1" } }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -10106,16 +11728,44 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + } + }, "is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "requires": { + "has-bigints": "^1.0.1" + } + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -10125,6 +11775,20 @@ "binary-extensions": "^2.0.0" } }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, "is-core-module": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", @@ -10133,6 +11797,19 @@ "has": "^1.0.3" } }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" + }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", @@ -10165,12 +11842,25 @@ "is-glob": "^2.0.0" } }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", @@ -10181,11 +11871,36 @@ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "requires": { + "call-bind": "^1.0.2" + } + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-string-and-not-blank": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/is-string-and-not-blank/-/is-string-and-not-blank-0.0.2.tgz", @@ -10199,6 +11914,22 @@ "resolved": "https://registry.npmjs.org/is-string-blank/-/is-string-blank-1.0.1.tgz", "integrity": "sha512-9H+ZBCVs3L9OYqv8nuUAzpcT9OTgMD1yAWrG7ihlnibdkbtB850heAmYWxHuXc4CHy4lKeK69tN+ny1K7gBIrw==" }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "requires": { + "which-typed-array": "^1.1.11" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -10212,6 +11943,22 @@ "is-invalid-path": "^0.1.0" } }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -10831,6 +12578,11 @@ } } }, + "js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -10855,6 +12607,11 @@ } } }, + "jsbi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz", + "integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==" + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -11294,6 +13051,26 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "mssql": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/mssql/-/mssql-9.1.3.tgz", + "integrity": "sha512-oXs2lJ1vKUe2s0twCdcdKnqATTVaIswzpSiGnUjMIhV6Sip9vEDuYt3dCoVWXXNuPJ5iFIqLxvagw4Hrz6xR4A==", + "requires": { + "@tediousjs/connection-string": "^0.4.1", + "commander": "^11.0.0", + "debug": "^4.3.3", + "rfdc": "^1.3.0", + "tarn": "^3.0.2", + "tedious": "^15.0.1" + }, + "dependencies": { + "commander": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", + "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==" + } + } + }, "multer": { "version": "1.4.5-lts.1", "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", @@ -11369,9 +13146,9 @@ } }, "nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", "optional": true }, "nanoclone": { @@ -11379,6 +13156,11 @@ "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" }, + "native-duplexpair": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", + "integrity": "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA==" + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -11401,6 +13183,11 @@ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" }, + "node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + }, "node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -11540,8 +13327,23 @@ "object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } }, "on-finished": { "version": "2.3.0", @@ -11581,6 +13383,16 @@ "mimic-fn": "^2.1.0" } }, + "open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -11920,6 +13732,16 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, + "regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + } + }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -12014,6 +13836,11 @@ "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.4.tgz", "integrity": "sha512-XgmCoxKWkDofwH8WddD0w85ZfqYz+ZHlr5yo+3YUCfycWawU56T5ckWXsScsj5B8tqUcIG67DxXByo3VUgiAdA==" }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, "rimraf": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", @@ -12023,6 +13850,24 @@ "glob": "^6.0.1" } }, + "safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + } + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -12034,6 +13879,16 @@ "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", "optional": true }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, "safe-stable-stringify": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", @@ -12206,7 +14061,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -12329,9 +14183,9 @@ "dev": true }, "sqlstring": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", - "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==" + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==" }, "sshpk": { "version": "1.17.0", @@ -12373,6 +14227,11 @@ "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" }, + "stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==" + }, "streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -12406,6 +14265,36 @@ "strip-ansi": "^6.0.1" } }, + "string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -12502,6 +14391,45 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "tarn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", + "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==" + }, + "tedious": { + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/tedious/-/tedious-15.1.3.tgz", + "integrity": "sha512-166EpRm5qknwhEisjZqz/mF7k14fXKJYHRg6XiAXVovd/YkyHJ3SG4Ppy89caPaNFfRr7PVYe+s4dAvKaCMFvw==", + "requires": { + "@azure/identity": "^2.0.4", + "@azure/keyvault-keys": "^4.4.0", + "@js-joda/core": "^5.2.0", + "bl": "^5.0.0", + "es-aggregate-error": "^1.0.8", + "iconv-lite": "^0.6.3", + "js-md4": "^0.3.2", + "jsbi": "^4.3.0", + "native-duplexpair": "^1.0.0", + "node-abort-controller": "^3.0.1", + "punycode": "^2.1.0", + "sprintf-js": "^1.1.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + } + } + }, "test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -12701,6 +14629,49 @@ "mime-types": "~2.1.24" } }, + "typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + } + }, + "typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + } + }, + "typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + } + }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -12714,6 +14685,17 @@ "bluebird": "^3.7.2" } }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, "undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -12896,6 +14878,30 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-typed-array": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, "winston": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/winston/-/winston-3.7.2.tgz", diff --git a/server/package.json b/server/package.json index fea0c1a5e..7760995e8 100644 --- a/server/package.json +++ b/server/package.json @@ -4,7 +4,7 @@ "description": "", "main": "server.js", "scripts": { - "test": "jest --silent --watchAll", + "test": "jest --silent", "bootstrap-server": "(npm install) && (npm run createSchema) && (npm run migrations) && (npm run seeds) && (nodemon server.js)", "createSchema": "npx sequelize db:create tombolo", "migrations": "npx sequelize db:migrate", @@ -29,6 +29,7 @@ "lodash": "^4.17.20", "moment": "^2.29.4", "morgan": "^1.10.0", + "mssql": "^9.1.3", "multer": "^1.4.5-lts.1", "mysql2": "^1.7.0", "nodemailer": "^6.7.1", @@ -43,6 +44,7 @@ "sequelize-cli": "^6.4.1", "simple-git": "^3.6.0", "socket.io": "^4.6.1", + "sqlstring": "^2.3.3", "tmp": "^0.1.0", "uuid": "^3.4.0", "winston": "^3.7.2" diff --git a/server/routes/integrations/read.js b/server/routes/integrations/read.js new file mode 100644 index 000000000..8000d8e45 --- /dev/null +++ b/server/routes/integrations/read.js @@ -0,0 +1,108 @@ +const models = require("../../models"); +const integrations = models.integrations; +let application = models.application; +const express = require("express"); +const router = express.Router(); +const path = require("path"); +const fs = require("fs"); +const rootENV = path.join(process.cwd(), "..", ".env"); +const serverENV = path.join(process.cwd(), ".env"); +const ENVPath = fs.existsSync(rootENV) ? rootENV : serverENV; +const { param, validationResult } = require("express-validator"); +const validatorUtil = require("../../utils/validator"); +require("dotenv").config({ path: ENVPath }); + +router.get( + "/get/:application_id", + [param("application_id").isUUID(4).withMessage("Invalid application id")], + async (req, res) => { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + try { + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + const { application_id } = req.params; + if (!application_id) throw Error("Invalid app ID"); + + const result = await integrations.findAll({ + where: { + application_id, + }, + }); + + res.status(200).send(result); + } catch (err) { + // ... error checks + console.log(err); + } + } +); + +//activate or deactive integration +router.put( + "/toggle/:application_id/:name", + [param("application_id").isUUID(4).withMessage("Invalid application id")], + async (req, res) => { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + try { + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + const { application_id, name } = req.params; + + const integration = await integrations.findOne({ + where: { application_id, name }, + raw: true, + }); + + const { active, id } = integration; + + // flipping Active + await integrations.update({ active: !active }, { where: { id } }); + + res.status(200).send("integration toggled succesfully"); + } catch (err) { + // ... error checks + console.log(err); + } + } +); + +//update integration notifications +router.put( + "/update/:application_id/:name", + [param("application_id").isUUID(4).withMessage("Invalid application id")], + async (req, res) => { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + try { + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + const { application_id, name } = req.params; + + if (!application_id) throw Error("Invalid app ID"); + const oldintegration = await integrations.findOne({ + where: { application_id, name }, + raw: true, + }); + + const { id } = oldintegration; + + // adjusting + await integrations.update( + { metaData: req.body.notifications, config: req.body.active }, + { where: { id } } + ); + + res.status(200).send("integration updated succesfully"); + } catch (err) { + // ... error checks + console.log(err); + } + } +); + +module.exports = router; diff --git a/server/routes/notifications/read.js b/server/routes/notifications/read.js index a3e79c886..185ec62b6 100644 --- a/server/routes/notifications/read.js +++ b/server/routes/notifications/read.js @@ -64,15 +64,13 @@ router.get( "/:applicationId", [param("applicationId").isUUID(4).withMessage("Invalid application id")], async (req, res) => { - const errors = validationResult(req).formatWith( - validatorUtil.errorFormatter - ); + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); try { - if (!errors.isEmpty()) - return res - .status(422) - .json({ success: false, errors: errors.array() }); + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); const { applicationId: application_id } = req.params; if (!application_id) throw Error("Invalid app ID"); const notifications = await monitoring_notifications.findAll({ diff --git a/server/routes/orbit/read.js b/server/routes/orbit/read.js new file mode 100644 index 000000000..b63c657a7 --- /dev/null +++ b/server/routes/orbit/read.js @@ -0,0 +1,840 @@ +const models = require("../../models"); +const orbitBuilds = models.orbitBuilds; +const orbitMonitoring = models.orbitMonitoring; +const express = require("express"); +const { param, body, validationResult } = require("express-validator"); +const { Op } = require("sequelize"); +const moment = require("moment"); +const router = express.Router(); +const path = require("path"); +const fs = require("fs"); +const rootENV = path.join(process.cwd(), "..", ".env"); +const serverENV = path.join(process.cwd(), ".env"); +const ENVPath = fs.existsSync(rootENV) ? rootENV : serverENV; +const validatorUtil = require("../../utils/validator"); +const monitoring_notifications = models.monitoring_notifications; +const notificationTemplate = require("../../jobs/messageCards/notificationTemplate"); +const { notify } = require("../notifications/email-notification"); +const { v4: uuidv4 } = require("uuid"); +const axios = require("axios"); +const SqlString = require("sqlstring"); + +const jobScheduler = require("../../job-scheduler"); + +const sql = require("mssql"); +const db = require("../../models"); + +const dbConfig = { + server: process.env.ORBIT_DB, + database: process.env.ORBIT_DB_NAME, + user: process.env.ORBIT_DB_USER, + password: process.env.ORBIT_DB_PWD, + port: parseInt(process.env.ORBIT_DB_PORT), + trustServerCertificate: true, +}; + +const fidoDbConfig = { + server: process.env.FIDO_DB, + database: process.env.FIDO_DB_NAME, + user: process.env.FIDO_DB_USER, + password: process.env.FIDO_DB_PWD, + port: parseInt(process.env.FIDO_DB_PORT), + trustServerCertificate: true, +}; + +const runSQLQuery = async (query, config) => { + try { + await sql.connect(config); + + const result = await sql.query(query); + + //need this close to fix bug where it was only contacting the first server + sql.close(); + return result; + } catch (err) { + console.log(err); + return { + err, + message: "There was an issue contacting the server", + }; + } +}; + +require("dotenv").config({ path: ENVPath }); + +//create one monitoring +//TODO get workunits from past 2 weeks as well in orbitbuilds table +router.post( + "/", + [ + body("application_id").isUUID(4).withMessage("Invalid application id"), + body("cron").custom((value) => { + const valArray = value.split(" "); + if (valArray.length > 5) { + throw new Error( + `Expected number of cron parts 5, received ${valArray.length}` + ); + } else { + return Promise.resolve("Good to go"); + } + }), + ], + async (req, res) => { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + try { + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + + // get last status and WU to store against future checks + const query = `select TOP 1 HpccWorkUnit as 'WorkUnit', Name as 'Build', DateUpdated as 'Date', Status_Code as 'Status' from DimBuildInstance where Name = ${SqlString.escape( + req.body.build + )} order by Date desc`; + + const wuResult = await runSQLQuery(query, dbConfig); + + if (wuResult?.err) { + throw Error(result.message); + } + + //destructure out of recordset and place inside of new metaData + const { WorkUnit, Date, Status } = wuResult.recordset[0]; + + const metaData = req.body.metaData; + + metaData.lastWorkUnit = { + lastWorkUnit: WorkUnit, + lastWorkUnitDate: Date, + lastWorkUnitStatus: Status, + }; + + //null out isActive in metaData to reduce noise + metaData.isActive = null; + + // TODO transform data before sending in for easier updates + let newBuildData = { + application_id: req.body.application_id, + cron: req.body.cron, + name: req.body.name, + build: req.body.build, + severityCode: req.body.severityCode, + product: req.body.product, + businessUnit: req.body.businessUnit, + host: req.body.host, + primaryContact: req.body.primaryContact, + secondaryContact: req.body.secondaryContact, + metaData: metaData, + isActive: req.body.isActive, + }; + + const newOrbitMonitoring = await orbitMonitoring.create(newBuildData); + + const { isActive } = req.body; + + //Add monitoring to bree if start monitoring now is checked + if (isActive) { + const schedularOptions = { + orbitMonitoring_id: newOrbitMonitoring.dataValues.id, + cron: newOrbitMonitoring.cron, + }; + + jobScheduler.createOrbitMonitoringJob(schedularOptions); + } + + res.status(201).send(newOrbitMonitoring); + } catch (error) { + console.log(error); + res + .status(500) + .json({ message: "Unable to save Orbit monitoring details" }); + } + } +); + +//get all monitorings +router.get( + "/allMonitorings/:application_id", + [param("application_id").isUUID(4).withMessage("Invalid application id")], + async (req, res) => { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + try { + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + const { application_id } = req.params; + + const result = await orbitMonitoring.findAll({ + where: { + application_id, + }, + }); + + res.status(200).send(result); + } catch (err) { + // ... error checks + console.log(err); + } + } +); + +//get all +router.get( + "/allMonitoring/:application_id", + [param("application_id").isUUID(4).withMessage("Invalid application id")], + async (req, res) => { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + try { + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + const { application_id } = req.params; + if (!application_id) throw Error("Invalid app ID"); + const result = await orbitMonitoring.findAll({ + where: { + application_id, + }, + }); + + res.status(200).send(result); + } catch (err) { + // ... error checks + console.log(err); + } + } +); + +//search Database for builds with keyword +router.get( + "/search/:application_id/:keyword", + [param("application_id").isUUID(4).withMessage("Invalid application id")], + [ + param("keyword") + .matches(/^[a-zA-Z0-9_.\-:\*\? ]*$/) + .withMessage("Invalid keyword"), + ], + async (req, res) => { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + try { + console.log(errors); + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + const { application_id, keyword } = req.params; + if (!application_id) throw Error("Invalid app ID"); + + const keywordEscaped = SqlString.escape("%" + keyword + "%"); + + const query = `select Name from DimBuildInstance where Name like ${keywordEscaped} and Name not like 'Scrub%' and EnvironmentName = 'Insurance' order by Name asc`; + + const result = await runSQLQuery(query, dbConfig); + + if (result.err) { + throw Error(result.message); + } + + const uniqueNames = []; + + const unique = result.recordset.filter((element) => { + const isDuplicate = uniqueNames.includes(element.Name); + + if (!isDuplicate) { + uniqueNames.push(element.Name); + + return true; + } + + return false; + }); + + res.status(200).json(unique); + } catch (err) { + // ... error checks + + console.log(err); + res + .status(400) + .send("There was an issue contacting the orbit reports server"); + } + } +); + +/* get single build */ +router.get( + "/getOrbitBuildDetails/:buildName", + [ + param("buildName") + .matches(/^[a-zA-Z0-9_.\-:\*\? ]*$/) + .withMessage("Invalid build name"), + ], + + async (req, res) => { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + try { + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + + const { buildName } = req.params; + + //connect to db + + const query = `select Top 1 EnvironmentName, Name, Status_DateCreated, HpccWorkUnit, Status_Code, Substatus_Code, BuildInstanceIdKey from DimBuildInstance where Name = ${SqlString.escape( + buildName + )} order by Status_DateCreated desc`; + + const result = await runSQLQuery(query, dbConfig); + + if (result.err) { + throw Error(result.message); + } + + res.json(result?.recordset[0]); + } catch (err) { + // ... error checks + + console.log(err); + res + .status(400) + .send("There was an issue contacting the orbit reports server"); + } + } +); + +//update orbit monitoring +router.put( + "/", + [ + body("application_id").isUUID(4).withMessage("Invalid application id"), + body("cron").custom((value) => { + const valArray = value.split(" "); + if (valArray.length > 5) { + throw new Error( + `Expected number of cron parts 5, received ${valArray.length}` + ); + } else { + return Promise.resolve("Good to go"); + } + }), + ], + async (req, res) => { + try { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + + const oldInfo = await orbitMonitoring.findOne({ + where: { id: req.body.id }, + raw: true, + }); + + let newInfo = req.body; + + //destructure info out of sent info + const { + id, + name, + build, + notifyCondition, + severityCode, + product, + businessUnit, + host, + isActive, + application_id, + primaryContact, + secondaryContact, + cron, + metaData: { lastMonitored, monitoringCondition }, + } = newInfo; + + //CODEQL FIX + //----------------------- + let notificationChannels = req.body.notificationChannels; + + if (!(notificationChannels instanceof Array)) { + return []; + } + //----------------------- + + //build out notifications object for storing inside metadata + let emails, msTeamsGroups; + if (notificationChannels.includes("eMail")) { + emails = newInfo.emails; + } + if (notificationChannels.includes("msTeams")) { + msTeamsGroups = newInfo.msTeamsGroups; + } + + let notifications = []; + + for (let i = 0; i < notificationChannels.length; i++) { + if (notificationChannels[i] === "eMail") { + notifications.push({ channel: "eMail", recipients: emails }); + } + if (notificationChannels[i] === "msTeams") { + notifications.push({ + channel: "msTeams", + recipients: msTeamsGroups, + }); + } + } + const escapedBuild = SqlString.escape(build); + + //get most recent work unit for storage + // get last status and WU to store against future checks + const query = `select TOP 1 HpccWorkUnit as 'WorkUnit', Name as 'Build', DateUpdated as 'Date', Status_Code as 'Status' from DimBuildInstance where Name = ${escapedBuild} + order by Date desc`; + + const wuResult = await runSQLQuery(query, dbConfig); + + if (wuResult.err) { + throw Error(result.message); + } + + //destructure out of recordset and place inside of new metaData + const { WorkUnit, Date, Status } = wuResult.recordset[0]; + + //set data fields + newInfo = { + id, + name, + cron, + isActive, + build, + severityCode, + product, + businessUnit, + application_id, + host, + primaryContact, + secondaryContact, + metaData: { + lastWorkUnit: { + lastWorkUnit: WorkUnit, + lastWorkUnitDate: Date, + lastWorkUnitStatus: Status, + }, + lastMonitored, + notifications, + severityCode, + monitoringCondition, + }, + }; + // ------------------------------------------------------- + + await orbitMonitoring.update(newInfo, { where: { id } }); + + // If start monitoring was changed to TRUE + if (isActive && oldInfo.isActive === 0) { + const schedularOptions = { + orbitMonitoring_id: id, + cron: newOrbitMonitoring.cron, + }; + + jobScheduler.createOrbitMonitoringJob(schedularOptions); + } + + // If start monitoring was changed to FALSE + if (!isActive && oldInfo.isActive === 1) { + await jobScheduler.removeJobFromScheduler(`Orbit Monitoring - ${id}`); + } + + // if cron has changed + if (oldInfo.cron != cron) { + const allBreeJobs = jobScheduler.getAllJobs(); + const jobName = `Orbit Monitoring - ${id}`; + for (let job of allBreeJobs) { + if (job.name === jobName) { + await jobScheduler.removeJobFromScheduler(jobName); + await jobScheduler.createOrbitMonitoringJob({ + orbitMonitoring_id: id, + + cron: cron, + }); + } + } + } + + res.status(200).send(newInfo); + } catch (error) { + console.log(error); + res + .status(500) + .json({ message: "Unable to save orbit build monitoring details" }); + } + } +); + +// EVERYTHING ABOVE THIS WORKS WITH APP ID VALIDATION + +// Pause or start monitoring +router.put( + "/togglestatus/:id", + [param("id").isUUID(4).withMessage("Invalid orbit monitoring Id")], + async (req, res, next) => { + try { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + const { id } = req.params; + const monitoring = await orbitMonitoring.findOne({ + where: { id }, + raw: true, + }); + const { isActive } = monitoring; + + // flipping isActive + await orbitMonitoring.update( + { isActive: !isActive }, + { where: { id: id } } + ); + + // If isActive, it is in bre - remove from bree + if (isActive) { + await jobScheduler.removeJobFromScheduler(`Orbit Monitoring - ${id}`); + } + + const name = monitoring.name; + const cron = monitoring.cron; + + // If isActive = false, add it to bre + if (!isActive) { + await jobScheduler.createOrbitMonitoringJob({ + orbitMonitoring_id: id, + cron: cron, + }); + } + + res.status(200).send("Update successful"); + } catch (err) { + console.log(err); + } + } +); + +//delete +router.delete( + "/delete/:id/:name", + [param("id").isUUID(4).withMessage("Invalid orbit monitoring id")], + async (req, res, next) => { + try { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + const { id, name } = req.params; + const response = await orbitMonitoring.destroy({ + where: { id }, + }); + res.status(200).json({ message: `Deleted ${response} orbit monitoring` }); + + //Check if this job is in bree - if so - remove + const breeJobs = jobScheduler.getAllJobs(); + const expectedJobName = `Orbit Monitoring - ${id}`; + if (breeJobs?.length) { + for (job of breeJobs) { + if (job.name === expectedJobName) { + jobScheduler.removeJobFromScheduler(expectedJobName); + break; + } + } + } + } catch (err) { + res.status(500).json({ message: err.message }); + } + } +); + +router.get( + "/getOne/:application_id/:id", + [param("id").isUUID(4).withMessage("Invalid orbit id")], + [param("application_id").isUUID(4).withMessage("Invalid application id")], + async (req, res) => { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + try { + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + + const { id, application_id } = req.params; + + const result = await orbitMonitoring.findOne({ + where: { id, application_id }, + raw: true, + }); + + res.status(200).send(result); + } catch (err) { + res.status(500).json({ message: err.message }); + console.error(err); + } + } +); + +router.get( + "/getWorkunits/:application_id", + [param("application_id").isUUID(4).withMessage("Invalid application id")], + async (req, res) => { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + try { + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + + const { application_id } = req.params; + + const result = await orbitMonitoring.findAll({ + where: { application_id }, + raw: true, + }); + + //connect to db + + let wuList = []; + if (result?.length) { + await Promise.all( + result.map(async (build) => { + let wu = await orbitBuilds.findAll({ + where: { application_id, name: build.build }, + raw: true, + }); + + if (wu.length > 0) { + wu.map((wu) => { + wuList.push(wu); + }); + } + + Promise.resolve; + }) + ); + } + //return finished list; + res.status(200).send(wuList); + } catch (err) { + res.status(500).json({ message: err.message }); + console.error(err); + } + } +); + +//refresh data, grab new builds +router.post( + "/updateList/:application_id", + [param("application_id").isUUID(4).withMessage("Invalid application id")], + async (req, res) => { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + try { + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + const { application_id } = req.params; + if (!application_id) throw Error("Invalid app ID"); + + const query = + "select TOP 10 * from DimBuildInstance where SubStatus_Code = 'MEGAPHONE' order by DateUpdated desc"; + + const result = runSQLQuery(query, dbConfig); + + const sentNotifications = []; + //just grab the rows from result + let rows = result?.recordset; + + if (rows?.length) { + //loop through rows to build notifications and import + await Promise.all( + rows?.map(async (build) => { + //check if the build already exists + let orbitBuild = await orbitBuilds.findOne({ + where: { + build_id: build.BuildInstanceIdKey, + application_id: application_id, + }, + raw: true, + }); + + //if it doesn't exist, create it and send a notification + if (!orbitBuild) { + //create build + const newBuild = await orbitBuilds.create({ + application_id: application_id, + build_id: build.BuildInstanceIdKey, + name: build.Name, + metaData: { + lastRun: build.DateUpdated, + status: build.Status_Code, + subStatus: build.SubStatus_Code, + workunit: build.HpccWorkUnit, + EnvironmentName: build.EnvironmentName, + Template: build.BuildTemplate_Name, + }, + }); + + //if megaphone, send notification + // if (build.SubStatus_Code === "MEGAPHONE") + + //build and send email notification + if (integration.metaData.notificationEmails) { + let buildDetails = { + name: newBuild.name, + status: newBuild.metaData.status, + subStatus: newBuild.metaData.subStatus, + lastRun: newBuild.metaData.lastRun, + workunit: newBuild.metaData.workunit, + }; + + const emailBody = + notificationTemplate.orbitBuildEmailBody(buildDetails); + const emailRecipients = integration.metaData.notificationEmails; + + const notificationResponse = await notify({ + to: emailRecipients, + from: process.env.EMAIL_SENDER, + subject: + "Alert: Megaphone Substatus detected on Orbit Build " + + build.Name, + text: emailBody, + html: emailBody, + }); + + let notification_id = uuidv4(); + + sentNotifications.push({ + id: notification_id, + status: "notified", + notifiedTo: emailRecipients, + notification_channel: "eMail", + application_id, + notification_reason: "Megaphone Substatus", + monitoring_id: newBuild.id, + monitoring_type: "orbit", + }); + } + + // //build and send Teams notification + if (integration.metaData.notificationWebhooks) { + let facts = [ + { name: newBuild.name }, + { status: newBuild.metaData.status }, + { subStatus: newBuild.metaData.subStatus }, + { lastRun: newBuild.metaData.lastRun }, + { workunit: newBuild.metaData.workunit }, + ]; + let title = "Orbit Build Detectd With Megaphone Status"; + notification_id = uuidv4(); + const cardBody = notificationTemplate.orbitBuildMessageCard( + title, + facts, + notification_id + ); + await axios.post( + integration.metaData.notificationWebhooks, + cardBody + ); + + sentNotifications.push({ + id: notification_id, + status: "notified", + notifiedTo: emailRecipients, + notification_channel: "msTeams", + application_id, + notification_reason: "Megaphone Substatus", + monitoring_id: newBuild.id, + monitoring_type: "orbit", + }); + } + } + + return true; + }) + ); + } + + // Record notifications + if (sentNotifications.length > 0) { + monitoring_notifications.bulkCreate(sentNotifications); + } + + res.status(200).send(result); + } catch (err) { + // ... error checks + + console.log(err); + res + .status(400) + .send("There was an issue contacting the orbit reports server"); + } + } +); + +router.get( + "/getDomains/:application_id", + [param("application_id").isUUID(4).withMessage("Invalid application id")], + async (req, res) => { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + try { + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + const query = + "select business_unit from pbi.dim_asr_domain_v where business_unit != 'Unassigned' AND business_unit != 'Not Applicable' order by business_unit asc"; + const result = await runSQLQuery(query, fidoDbConfig); + + if (result?.recordset) { + res.status(200).send(result.recordset); + } else { + throw Error("No domains found on Fido Server: " + query); + } + } catch (err) { + console.log(err); + res + .status(400) + .send("There was an issue contacting the orbit reports server"); + } + } +); + +router.get( + "/getProducts/:application_id", + [param("application_id").isUUID(4).withMessage("Invalid application id")], + async (req, res) => { + const errors = validationResult(req).formatWith( + validatorUtil.errorFormatter + ); + try { + if (!errors.isEmpty()) + return res.status(422).json({ success: false, errors: errors.array() }); + const query = + "select product_name from pbi.dim_asr_product_v order by product_name asc"; + const result = await runSQLQuery(query, fidoDbConfig); + + if (result?.recordset) { + res.status(200).send(result.recordset); + } else { + throw Error("No products found on Fido Server: " + query); + } + } catch (err) { + console.log(err); + res + .status(400) + .send("There was an issue contacting the orbit reports server"); + } + } +); +module.exports = router; diff --git a/server/server.js b/server/server.js index 9eea279a7..ab34e9f7e 100644 --- a/server/server.js +++ b/server/server.js @@ -75,6 +75,8 @@ const api = require("./routes/api/read"); const jobmonitoring = require("./routes/jobmonitoring/read"); const superfileMonitoring = require("./routes/superfilemonitoring/read"); const cluster = require("./routes/cluster/read"); +const orbit = require("./routes/orbit/read"); +const integrations = require("./routes/integrations/read"); const teamsHook = require("./routes/msTeamsHook/read"); app.use("/api/user", userRead); @@ -111,6 +113,8 @@ app.use("/api/clustermonitoring", clustermonitoring); app.use("/api/key", key); app.use("/api/jobmonitoring", jobmonitoring); app.use("/api/cluster", cluster); +app.use("/api/orbit", orbit); +app.use("/api/integrations", integrations); app.use("/api/teamsHook", teamsHook); app.use((err, req, res, next) => { @@ -118,7 +122,7 @@ app.use((err, req, res, next) => { res.status(500).send("Something went wrong"); }); -process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; +// process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; /* Start server */ server.listen(port, "0.0.0.0", async () => { diff --git a/server/tempFiles/Tombolo-Notifications.CSV b/server/tempFiles/Tombolo-Notifications.CSV deleted file mode 100644 index 045f13370..000000000 --- a/server/tempFiles/Tombolo-Notifications.CSV +++ /dev/null @@ -1 +0,0 @@ -id,monitoringId,Channel,Reason,Status,Created,Deleted \ No newline at end of file diff --git a/server/tempFiles/Tombolo-Notifications.JSON b/server/tempFiles/Tombolo-Notifications.JSON deleted file mode 100644 index 0637a088a..000000000 --- a/server/tempFiles/Tombolo-Notifications.JSON +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/server/tempFiles/Tombolo-clusterUsage.CSV b/server/tempFiles/Tombolo-clusterUsage.CSV deleted file mode 100644 index 08ed77c3a..000000000 --- a/server/tempFiles/Tombolo-clusterUsage.CSV +++ /dev/null @@ -1,34 +0,0 @@ -type,date,maxUsage,meanUsage -thor,Mon Jun 19 2023 16:18:46 GMT-0400 (Eastern Daylight Time),32,20.65 -thor,Tue Jun 13 2023 16:15:00 GMT-0400 (Eastern Daylight Time),32,20.65 -thor,Mon Jun 12 2023 23:06:56 GMT-0400 (Eastern Daylight Time),32,20.65 -thor,Mon Jun 12 2023 19:06:56 GMT-0400 (Eastern Daylight Time),32,20.65 -thor,Mon Jun 12 2023 15:06:55 GMT-0400 (Eastern Daylight Time),32,20.65 -thor,Fri Jun 09 2023 20:53:34 GMT-0400 (Eastern Daylight Time),32,20.65 -thor,Fri Jun 09 2023 16:53:33 GMT-0400 (Eastern Daylight Time),32,20.65 -thor,Fri Jun 09 2023 12:53:33 GMT-0400 (Eastern Daylight Time),32,20.65 -thor,Thu Jun 08 2023 17:39:58 GMT-0400 (Eastern Daylight Time),32,20.65 -thor,Wed Jun 07 2023 19:50:27 GMT-0400 (Eastern Daylight Time),32,20.65 -thor,Wed Jun 07 2023 15:50:26 GMT-0400 (Eastern Daylight Time),32,20.65 -hthor,Mon Jun 19 2023 16:18:46 GMT-0400 (Eastern Daylight Time),18,18 -hthor,Tue Jun 13 2023 16:15:00 GMT-0400 (Eastern Daylight Time),18,18 -hthor,Mon Jun 12 2023 23:06:56 GMT-0400 (Eastern Daylight Time),18,18 -hthor,Mon Jun 12 2023 19:06:56 GMT-0400 (Eastern Daylight Time),18,18 -hthor,Mon Jun 12 2023 15:06:55 GMT-0400 (Eastern Daylight Time),18,18 -hthor,Fri Jun 09 2023 20:53:34 GMT-0400 (Eastern Daylight Time),18,18 -hthor,Fri Jun 09 2023 16:53:33 GMT-0400 (Eastern Daylight Time),18,18 -hthor,Fri Jun 09 2023 12:53:33 GMT-0400 (Eastern Daylight Time),18,18 -hthor,Thu Jun 08 2023 17:39:58 GMT-0400 (Eastern Daylight Time),18,18 -hthor,Wed Jun 07 2023 19:50:27 GMT-0400 (Eastern Daylight Time),18,18 -hthor,Wed Jun 07 2023 15:50:26 GMT-0400 (Eastern Daylight Time),18,18 -roxie,Mon Jun 19 2023 16:18:46 GMT-0400 (Eastern Daylight Time),18,13 -roxie,Tue Jun 13 2023 16:15:00 GMT-0400 (Eastern Daylight Time),18,13 -roxie,Mon Jun 12 2023 23:06:56 GMT-0400 (Eastern Daylight Time),18,13 -roxie,Mon Jun 12 2023 19:06:56 GMT-0400 (Eastern Daylight Time),18,13 -roxie,Mon Jun 12 2023 15:06:55 GMT-0400 (Eastern Daylight Time),18,13 -roxie,Fri Jun 09 2023 20:53:34 GMT-0400 (Eastern Daylight Time),18,13 -roxie,Fri Jun 09 2023 16:53:33 GMT-0400 (Eastern Daylight Time),18,13 -roxie,Fri Jun 09 2023 12:53:33 GMT-0400 (Eastern Daylight Time),18,13 -roxie,Thu Jun 08 2023 17:39:58 GMT-0400 (Eastern Daylight Time),18,13 -roxie,Wed Jun 07 2023 19:50:27 GMT-0400 (Eastern Daylight Time),18,13 -roxie,Wed Jun 07 2023 15:50:26 GMT-0400 (Eastern Daylight Time),18,13 \ No newline at end of file diff --git a/server/tempFiles/Tombolo-clusterUsage.JSON b/server/tempFiles/Tombolo-clusterUsage.JSON deleted file mode 100644 index c9a7d181b..000000000 --- a/server/tempFiles/Tombolo-clusterUsage.JSON +++ /dev/null @@ -1 +0,0 @@ -[{"thor":[{"date":"2023-06-19T20:18:46.324Z","maxUsage":32,"meanUsage":20.65},{"date":"2023-06-13T20:15:00.246Z","maxUsage":32,"meanUsage":20.65},{"date":"2023-06-13T03:06:56.044Z","maxUsage":32,"meanUsage":20.65},{"date":"2023-06-12T23:06:56.047Z","maxUsage":32,"meanUsage":20.65},{"date":"2023-06-12T19:06:55.764Z","maxUsage":32,"meanUsage":20.65},{"date":"2023-06-10T00:53:34.213Z","maxUsage":32,"meanUsage":20.65},{"date":"2023-06-09T20:53:33.757Z","maxUsage":32,"meanUsage":20.65},{"date":"2023-06-09T16:53:33.854Z","maxUsage":32,"meanUsage":20.65},{"date":"2023-06-08T21:39:58.257Z","maxUsage":32,"meanUsage":20.65},{"date":"2023-06-07T23:50:27.030Z","maxUsage":32,"meanUsage":20.65},{"date":"2023-06-07T19:50:26.108Z","maxUsage":32,"meanUsage":20.65}],"hthor":[{"date":"2023-06-19T20:18:46.324Z","maxUsage":18,"meanUsage":18},{"date":"2023-06-13T20:15:00.246Z","maxUsage":18,"meanUsage":18},{"date":"2023-06-13T03:06:56.044Z","maxUsage":18,"meanUsage":18},{"date":"2023-06-12T23:06:56.047Z","maxUsage":18,"meanUsage":18},{"date":"2023-06-12T19:06:55.764Z","maxUsage":18,"meanUsage":18},{"date":"2023-06-10T00:53:34.213Z","maxUsage":18,"meanUsage":18},{"date":"2023-06-09T20:53:33.757Z","maxUsage":18,"meanUsage":18},{"date":"2023-06-09T16:53:33.854Z","maxUsage":18,"meanUsage":18},{"date":"2023-06-08T21:39:58.257Z","maxUsage":18,"meanUsage":18},{"date":"2023-06-07T23:50:27.030Z","maxUsage":18,"meanUsage":18},{"date":"2023-06-07T19:50:26.108Z","maxUsage":18,"meanUsage":18}],"roxie":[{"date":"2023-06-19T20:18:46.324Z","maxUsage":18,"meanUsage":13},{"date":"2023-06-13T20:15:00.246Z","maxUsage":18,"meanUsage":13},{"date":"2023-06-13T03:06:56.044Z","maxUsage":18,"meanUsage":13},{"date":"2023-06-12T23:06:56.047Z","maxUsage":18,"meanUsage":13},{"date":"2023-06-12T19:06:55.764Z","maxUsage":18,"meanUsage":13},{"date":"2023-06-10T00:53:34.213Z","maxUsage":18,"meanUsage":13},{"date":"2023-06-09T20:53:33.757Z","maxUsage":18,"meanUsage":13},{"date":"2023-06-09T16:53:33.854Z","maxUsage":18,"meanUsage":13},{"date":"2023-06-08T21:39:58.257Z","maxUsage":18,"meanUsage":13},{"date":"2023-06-07T23:50:27.030Z","maxUsage":18,"meanUsage":13},{"date":"2023-06-07T19:50:26.108Z","maxUsage":18,"meanUsage":13}]}] \ No newline at end of file diff --git a/server/tests/mock-data/OrbitMonitoringBad.json b/server/tests/mock-data/OrbitMonitoringBad.json new file mode 100644 index 000000000..d3f54cf5e --- /dev/null +++ b/server/tests/mock-data/OrbitMonitoringBad.json @@ -0,0 +1,32 @@ +{ + "name": "test12342134", + "cron": "*/5 * * * *", + "build": "DL V1 Build", + "application_id": "badid", + "businessUnit": "Insurance", + "product": "DL", + "host": "test.test.com", + "severityCode": 2, + "notifyCondition": ["buildStatus"], + "buildStatus": ["discarded", "build_available_for_use"], + "isActive": 1, + "primaryContact": "test@test.com", + "secondaryContact": "test2@test.com", + "notificationChannels": ["eMail"], + "metaData": { + "lastMonitored": 1706544111312, + "monitoringCondition": { + "notifyCondition": ["buildStatus"], + "buildStatus": ["discarded", "build_available_for_use"] + }, + "notifications": [ + { + "channel": "eMail" + } + ], + "isActive": 1 + }, + "dataValues": { + "id": "11bf5b37-e0b8-42e0-8dcf-dc8c4aefc000" + } +} diff --git a/server/tests/mock-data/cluster.json b/server/tests/mock-data/cluster.json index 4f8b14cd0..be9904b86 100644 --- a/server/tests/mock-data/cluster.json +++ b/server/tests/mock-data/cluster.json @@ -1,4 +1,5 @@ { + "id": "625be1ec-6387-4802-93fc-0ae52f0ebde7", "thor_host": "thor", "thor_port": 1800, "username": "", diff --git a/server/tests/mock-data/global.json b/server/tests/mock-data/global.json new file mode 100644 index 000000000..dd4c80261 --- /dev/null +++ b/server/tests/mock-data/global.json @@ -0,0 +1,8 @@ +{ + "application_id": "11bf5b37-e0b8-42e0-8dcf-dc8c4aefc000", + "badApplicationId": "23123nbjkk123", + "clusterId": "11bf5b37-e0b8-42e0-8dcf-dc8c4aefc000", + "name": "test", + "keyword": "test2", + "id": "11bf5b37-e0b8-42e0-8dcf-dc8c4aefc000" +} diff --git a/server/tests/mock-data/integration.json b/server/tests/mock-data/integration.json new file mode 100644 index 000000000..9596e1f40 --- /dev/null +++ b/server/tests/mock-data/integration.json @@ -0,0 +1,4 @@ +{ + "active": true, + "id": "625be1ec-6387-4802-93fc-0ae52f0ebde7" +} diff --git a/server/tests/mock-data/orbitMonitoring.json b/server/tests/mock-data/orbitMonitoring.json new file mode 100644 index 000000000..edf2e73d7 --- /dev/null +++ b/server/tests/mock-data/orbitMonitoring.json @@ -0,0 +1,32 @@ +{ + "name": "test12342134", + "cron": "*/5 * * * *", + "build": "DL V1 Build", + "application_id": "11bf5b37-e0b8-42e0-8dcf-dc8c4aefc000", + "businessUnit": "Insurance", + "product": "DL", + "host": "test.test.com", + "severityCode": 2, + "notifyCondition": ["buildStatus"], + "buildStatus": ["discarded", "build_available_for_use"], + "isActive": 1, + "primaryContact": "test@test.com", + "secondaryContact": "test2@test.com", + "notificationChannels": ["eMail"], + "metaData": { + "lastMonitored": 1706544111312, + "monitoringCondition": { + "notifyCondition": ["buildStatus"], + "buildStatus": ["discarded", "build_available_for_use"] + }, + "notifications": [ + { + "channel": "eMail" + } + ], + "isActive": 1 + }, + "dataValues": { + "id": "11bf5b37-e0b8-42e0-8dcf-dc8c4aefc000" + } +} diff --git a/server/tests/mock-data/sqlData.json b/server/tests/mock-data/sqlData.json new file mode 100644 index 000000000..e9554c4bc --- /dev/null +++ b/server/tests/mock-data/sqlData.json @@ -0,0 +1,16 @@ +{ + "recordset": [ + { + "WorkUnit": "W20231229-030004-2", + "Date": "2023-12-29 12:50:46.4686170", + "Status": "QA_IN_PROGRESS", + "Name": "Test 1" + }, + { + "WorkUnit": "W20231229-030004-2", + "Date": "2023-12-29 12:50:46.4686170", + "Status": "QA_IN_PROGRESS", + "Name": "Test 2" + } + ] +} diff --git a/server/tests/unit/integration.test.js b/server/tests/unit/integration.test.js new file mode 100644 index 000000000..5835e8ebb --- /dev/null +++ b/server/tests/unit/integration.test.js @@ -0,0 +1,86 @@ +//dependencies to start app.js and make calls +const express = require("express"); +const request = require("supertest"); +const app = express(); +app.use(express.json()); + +//import model and spy functions that will be called +const models = require("../../models"); +const integrations = models.integrations; +integrations.findOne = jest.fn(() => { + return mockIntegrationData; +}); +integrations.findAll = jest.fn(); +integrations.update = jest.fn(); + +//bring in standard testing data +const mockData = require("../mock-data/global.json"); +const mockIntegrationData = require("../mock-data/integration.json"); + +//route and models imported for testing +const integration = require("../../routes/integrations/read"); +app.use("/api/integration", integration); + +//write tests +describe("Integration Tests", () => { + //globals needed for multiple tests + const { application_id, badApplicationId, name } = mockData; + let response; + + beforeEach(() => { + jest.resetModules(); // Most important - it clears the cache + response = null; + }); + + describe("Route Checks", () => { + test("Get all", async () => { + response = await request(app).get( + `/api/integration/get/${application_id}` + ); + expect(integrations.findAll).toHaveBeenCalledTimes(1); + expect(response.status).toBe(200); + }); + + test("Get all - Bad App ID", async () => { + response = await request(app).get( + `/api/integration/get/${badApplicationId}` + ); + expect(response.status).toBe(422); + expect(response.body.success).toBe(false); + }); + + test("toggle active", async () => { + response = await request(app).put( + `/api/integration/toggle/${application_id}/${name}` + ); + expect(response.status).toBe(200); + expect(integrations.findOne).toHaveBeenCalledTimes(1); + expect(integrations.update).toHaveBeenCalledTimes(1); + }); + + test("toggle active - Bad App ID", async () => { + response = await request(app).put( + `/api/integration/toggle/${badApplicationId}/${name}` + ); + expect(response.status).toBe(422); + expect(response.body.success).toBe(false); + }); + + test("update ", async () => { + response = await request(app).put( + `/api/integration/update/${application_id}/${name}` + ); + expect(integrations.findOne).toHaveBeenCalledTimes(1); + expect(integrations.update).toHaveBeenCalledTimes(1); + expect(response.status).toBe(200); + }); + + test("update - Bad App ID", async () => { + response = await request(app).put( + `/api/integration/update/${badApplicationId}/${name}` + ); + expect(response.status).toBe(422); + expect(response.body.success).toBe(false); + }); + }); +}); diff --git a/server/tests/unit/notification.test.js b/server/tests/unit/notification.test.js index cc9debea0..08380fa8b 100644 --- a/server/tests/unit/notification.test.js +++ b/server/tests/unit/notification.test.js @@ -33,13 +33,13 @@ describe("Notifications Tests", () => { expect(response.status).toBe(200); }); - test("Get all - Bad Application ID", async () => { - response = await request(app).get( - `/api/notifications/read/badapplicationid` - ); - expect(response.status).toBe(422); - expect(response.body.success).toBe(false); - }); + // test("Get all - Bad Application ID", async () => { + // response = await request(app).get( + // `/api/notifications/read/badapplicationid` + // ); + // expect(response.status).toBe(422); + // expect(response.body.success).toBe(false); + // }); test("Get CSV File", async () => { response = await request(app).get( diff --git a/server/tests/unit/orbit.test.js b/server/tests/unit/orbit.test.js new file mode 100644 index 000000000..d8c7f504c --- /dev/null +++ b/server/tests/unit/orbit.test.js @@ -0,0 +1,279 @@ +//dependencies to start app.js and make calls +const express = require("express"); +const request = require("supertest"); +const app = express(); +app.use(express.json()); +const sql = require("mssql"); + +//bring in standard testing data +const mockData = require("../mock-data/global.json"); +const mockOrbitMonitoringData = require("../mock-data/orbitMonitoring.json"); +const mockOrbitMonitoringDataBadID = require("../mock-data/orbitMonitoringBad.json"); +const mockSqlReturnData = require("../mock-data/sqlData.json"); + +//import model and spy functions that will be called +const models = require("../../models"); +const jobScheduler = require("../../job-scheduler"); +const orbitBuild = models.orbitBuilds; +const orbitMonitoring = models.orbitMonitoring; +const monitoring_notifications = models.monitoring_notifications; + +//orbit Builds mocks +orbitBuild.findOne = jest.fn(); +orbitBuild.findAll = jest.fn(); +orbitBuild.update = jest.fn(); +orbitBuild.create = jest.fn(); +orbitBuild.destroy = jest.fn(); + +//orbit Monitoring mocks +orbitMonitoring.findOne = jest.fn(() => { + return mockOrbitMonitoringData; +}); +orbitMonitoring.findAll = jest.fn(() => { + return mockOrbitMonitoringData; +}); +orbitMonitoring.update = jest.fn(); +orbitMonitoring.create = jest.fn(() => { + return mockOrbitMonitoringData; +}); +orbitMonitoring.destroy = jest.fn(); + +//monitoring notifications mocks +monitoring_notifications.bulkCreate = jest.fn(); + +//job scheduler mocks +jobScheduler.createOrbitMonitoringJob = jest.fn(); +jobScheduler.removeJobFromScheduler = jest.fn(); +jobScheduler.getAllJobs = jest.fn(); + +//sql mocks +sql.connect = jest.fn(); +sql.query = jest.fn(() => { + return mockSqlReturnData; +}); + +//route and models imported for testing +const orbit = require("../../routes/orbit/read"); +const orbitbuilds = require("../../models/orbitbuilds"); +app.use("/api/orbit", orbit); + +describe("Integration Tests", () => { + //globals needed for multiple tests + const { application_id, badApplicationId, name, keyword, id } = mockData; + let response; + + beforeEach(() => { + jest.resetModules(); // Most important - it clears the cache + response = null; + }); + + describe("Route Checks", () => { + test("Create One", async () => { + response = await request(app) + .post(`/api/orbit/`) + .send(mockOrbitMonitoringData) + .set("Content-Type", "application/json") + .set("Accept", "application/json"); + + expect(orbitMonitoring.create).toHaveBeenCalledTimes(1); + expect(sql.query).toHaveBeenCalledTimes(1); + expect(response.status).toBe(201); + }); + + test("Create One - Bad App ID", async () => { + response = await request(app) + .post(`/api/orbit/`) + .send(mockOrbitMonitoringDataBadID) + .set("Content-Type", "application/json") + .set("Accept", "application/json"); + + expect(response.status).toBe(422); + expect(response.body.success).toBe(false); + }); + + test("Get All Monitorings", async () => { + response = await request(app).get( + `/api/orbit/allMonitorings/${application_id}` + ); + expect(orbitMonitoring.findAll).toHaveBeenCalledTimes(1); + expect(response.status).toBe(200); + }); + + test("Get All Monitorings - Bad Data", async () => { + response = await request(app).get( + `/api/orbit/allMonitorings/${badApplicationId}` + ); + expect(orbitMonitoring.findAll).toHaveBeenCalledTimes(0); + expect(response.status).toBe(422); + expect(response.body.success).toBe(false); + }); + + test("Get All Builds", async () => { + response = await request(app).get( + `/api/orbit/allMonitorings/${application_id}` + ); + expect(orbitMonitoring.findAll).toHaveBeenCalledTimes(1); + expect(response.status).toBe(200); + }); + + test("Get All Builds - Bad Data", async () => { + response = await request(app).get( + `/api/orbit/allMonitorings/${badApplicationId}` + ); + expect(orbitMonitoring.findAll).toHaveBeenCalledTimes(0); + expect(response.status).toBe(422); + expect(response.body.success).toBe(false); + }); + + test("Get Searched Results", async () => { + response = await request(app).get( + `/api/orbit/search/${application_id}/${keyword}` + ); + expect(sql.query).toHaveBeenCalledTimes(1); + expect(response.status).toBe(200); + }); + + test("Get Searched Results - Bad Data", async () => { + response = await request(app).get( + `/api/orbit/search/${badApplicationId}/${keyword}` + ); + expect(response.status).toBe(422); + expect(response.body.success).toBe(false); + }); + + test("Get Build Details from SQL", async () => { + response = await request(app).get( + `/api/orbit/getOrbitBuildDetails/${name}` + ); + expect(sql.query).toHaveBeenCalledTimes(1); + expect(response.status).toBe(200); + }); + + test("Get Build Details from SQL", async () => { + response = await request(app).get( + `/api/orbit/getOrbitBuildDetails/${"&"}` + ); + expect(sql.query).toHaveBeenCalledTimes(0); + expect(response.status).toBe(422); + expect(response.body.success).toBe(false); + }); + + test("Update Monitoring", async () => { + response = await request(app) + .put(`/api/orbit/`) + .send(mockOrbitMonitoringData) + .set("Content-Type", "application/json") + .set("Accept", "application/json"); + + expect(orbitMonitoring.findOne).toHaveBeenCalledTimes(1); + expect(orbitMonitoring.update).toHaveBeenCalledTimes(1); + expect(sql.connect).toHaveBeenCalledTimes(1); + expect(sql.query).toHaveBeenCalledTimes(1); + expect(response.status).toBe(200); + }); + + test("Update Monitoring", async () => { + response = await request(app) + .put(`/api/orbit/`) + .send(mockOrbitMonitoringDataBadID) + .set("Content-Type", "application/json") + .set("Accept", "application/json"); + + expect(orbitMonitoring.findOne).toHaveBeenCalledTimes(0); + expect(orbitMonitoring.update).toHaveBeenCalledTimes(0); + expect(response.status).toBe(422); + expect(response.body.success).toBe(false); + }); + + test("toggle status", async () => { + response = await request(app).put( + `/api/orbit/togglestatus/${application_id}` + ); + expect(orbitMonitoring.findOne).toHaveBeenCalledTimes(1); + expect(orbitMonitoring.update).toHaveBeenCalledTimes(1); + expect(response.status).toBe(200); + }); + + test("toggle status - bad id", async () => { + response = await request(app).put( + `/api/orbit/togglestatus/${badApplicationId}` + ); + expect(orbitMonitoring.findOne).toHaveBeenCalledTimes(0); + expect(orbitMonitoring.update).toHaveBeenCalledTimes(0); + expect(response.status).toBe(422); + expect(response.body.success).toBe(false); + }); + + test("Delete", async () => { + response = await request(app).delete( + `/api/orbit/delete/${application_id}/${name}` + ); + expect(orbitMonitoring.destroy).toHaveBeenCalledTimes(1); + expect(response.status).toBe(200); + }); + + test("Delete", async () => { + response = await request(app).delete( + `/api/orbit/delete/${badApplicationId}/${name}` + ); + expect(orbitMonitoring.destroy).toHaveBeenCalledTimes(0); + expect(response.status).toBe(422); + expect(response.body.success).toBe(false); + }); + + test("Get one", async () => { + response = await request(app).get( + `/api/orbit/getOne/${application_id}/${id}` + ); + expect(orbitMonitoring.findOne).toHaveBeenCalledTimes(1); + expect(response.status).toBe(200); + }); + + test("Get one - Bad ID", async () => { + response = await request(app).get( + `/api/orbit/getOne/${badApplicationId}/${id}` + ); + expect(orbitMonitoring.findOne).toHaveBeenCalledTimes(0); + expect(response.status).toBe(422); + expect(response.body.success).toBe(false); + }); + + test("Get WorkUnits", async () => { + response = await request(app).get( + `/api/orbit/getWorkunits/${application_id}` + ); + expect(orbitMonitoring.findAll).toHaveBeenCalledTimes(1); + expect(response.status).toBe(200); + }); + + test("Get WorkUnits - Bad ID", async () => { + response = await request(app).get( + `/api/orbit/getWorkunits/${badApplicationId}` + ); + expect(orbitMonitoring.findAll).toHaveBeenCalledTimes(0); + expect(orbitBuild.findAll).toHaveBeenCalledTimes(0); + expect(response.status).toBe(422); + expect(response.body.success).toBe(false); + }); + + test("Update List", async () => { + response = await request(app).post( + `/api/orbit/updateList/${application_id}` + ); + + expect(sql.connect).toHaveBeenCalledTimes(1); + expect(sql.query).toHaveBeenCalledTimes(1); + + expect(response.status).toBe(200); + }); + + test("Update List - Bad ID", async () => { + response = await request(app).post( + `/api/orbit/updateList/${badApplicationId}` + ); + + expect(response.status).toBe(422); + expect(response.body.success).toBe(false); + }); + }); +});