diff --git a/package.json b/package.json index bdbf96c..475a798 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,8 @@ "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "nodemon": "^3.1.4", - "ts-node": "^10.9.1", "ts-jest": "^29.0.3", + "ts-node": "^10.9.1", "typescript": "^4.6.4" }, "dependencies": { diff --git a/src/client/src/lib/api/monitorsApi.ts b/src/client/src/lib/api/monitorsApi.ts index e9b430b..12d8b2a 100644 --- a/src/client/src/lib/api/monitorsApi.ts +++ b/src/client/src/lib/api/monitorsApi.ts @@ -1,6 +1,11 @@ import { performApiCall } from './utils'; -const monitorsApi = { getMyMonitors, getMyMonitorSummary, createMonitor }; +const monitorsApi = { + getMyMonitors, + getMyMonitorSummary, + fetchMonitorsFromUptimeRobot, + createMonitor, +}; async function createMonitor(params: { displayName: string; frequency: number; url: string }) { const URI = `me/monitors`; @@ -14,6 +19,13 @@ async function getMyMonitors() { return performApiCall(URI, 'GET'); } +type appMonitorDtoType = { displayName: string; frequency: number; url: string }; + +async function fetchMonitorsFromUptimeRobot(uptimeRobotApiKey: string) { + const URI = `uptime-robot/monitors`; + return performApiCall(URI, 'POST', { uptimeRobotApiKey }); +} + type statusValueType = 'up' | 'down'; type eventType = { createdAt: string; kind: statusValueType; title: string; id: number }; @@ -30,3 +42,4 @@ async function getMyMonitorSummary(monitorId: string) { } export { monitorsApi }; +export type { appMonitorDtoType }; diff --git a/src/client/src/lib/api/utils.ts b/src/client/src/lib/api/utils.ts index 1138b85..831bc3f 100644 --- a/src/client/src/lib/api/utils.ts +++ b/src/client/src/lib/api/utils.ts @@ -9,23 +9,19 @@ async function performApiCall( const url = `${BASE_URL}/${uri}`; let response: Response; const token = localStorage.jwtTokenHandler.get(); - - if (method === 'GET') { - response = await fetch(url, { method, headers: { Authorization: `Bearer ${token}` } }); - } else { - const headers: Record = { - Accept: 'application/json', - 'Content-Type': 'application/json', - }; - if (token) { - headers['Authorization'] = `Bearer ${token}`; - } - response = await fetch(url, { - method, - headers, - body: JSON.stringify(body), - }); + const headers: Record = { + Accept: 'application/json', + 'Content-Type': 'application/json', + }; + if (token) { + headers['Authorization'] = `Bearer ${token}`; } + + response = await fetch(url, { + method, + headers, + body: JSON.stringify(body), + }); if (!response.ok) { if (response.status === 401) { localStorage.jwtTokenHandler.remove(); diff --git a/src/client/src/locale/fr.ts b/src/client/src/locale/fr.ts index 4a8768b..0f50e5f 100644 --- a/src/client/src/locale/fr.ts +++ b/src/client/src/locale/fr.ts @@ -2,6 +2,9 @@ const fr = { importFrom: { uptimeRobot: { fetchMonitorsButton: 'Récupérer les monitors', + list: { + frequencyLabel: 'toutes les {{frequency}} minutes', + }, }, }, monitors: { diff --git a/src/client/src/locale/utils.test.ts b/src/client/src/locale/utils.test.ts new file mode 100644 index 0000000..feb0b4d --- /dev/null +++ b/src/client/src/locale/utils.test.ts @@ -0,0 +1,37 @@ +import { variabilize } from './utils'; + +describe('utils', () => { + describe('variabilize', () => { + it('should return the original text if no variable pass', () => { + const text = 'truc'; + + const variabilizedText = variabilize(text, {}); + + expect(variabilizedText).toBe(text); + }); + + it('should return the variablized text if right variable pass', () => { + const text = 'truc {{count}} machin'; + + const variabilizedText = variabilize(text, { count: 2 }); + + expect(variabilizedText).toBe('truc 2 machin'); + }); + + it('should return the variablized text if right variable passed twice', () => { + const text = 'truc {{count}} machin {{count}}'; + + const variabilizedText = variabilize(text, { count: 2 }); + + expect(variabilizedText).toBe('truc 2 machin 2'); + }); + + it('should return the variablized text if several variables passed', () => { + const text = 'truc {{count}} machin {{double}}'; + + const variabilizedText = variabilize(text, { count: 2, double: 4 }); + + expect(variabilizedText).toBe('truc 2 machin 4'); + }); + }); +}); diff --git a/src/client/src/locale/utils.ts b/src/client/src/locale/utils.ts new file mode 100644 index 0000000..ad81217 --- /dev/null +++ b/src/client/src/locale/utils.ts @@ -0,0 +1,9 @@ +function variabilize(text: string, variables: Record) { + let variabilizedText = text; + for (const [key, value] of Object.entries(variables)) { + const REGEX = new RegExp(`{{${key}}}`, 'g'); + variabilizedText = variabilizedText.replace(REGEX, `${value}`); + } + return variabilizedText; +} +export { variabilize }; diff --git a/src/client/src/pages/ImportFrom/AppMonitorList.tsx b/src/client/src/pages/ImportFrom/AppMonitorList.tsx new file mode 100644 index 0000000..ca02fd4 --- /dev/null +++ b/src/client/src/pages/ImportFrom/AppMonitorList.tsx @@ -0,0 +1,60 @@ +import { + Checkbox, + List, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, +} from '@mui/material'; +import { appMonitorDtoType } from '../../lib/api/monitorsApi'; +import { useState } from 'react'; +import { variabilize } from '../../locale/utils'; +import { locale } from '../../locale'; + +function AppMonitorList(props: { appMonitorsDtos: appMonitorDtoType[] }) { + const initialSelectedIndexes = props.appMonitorsDtos.map((_, index) => index); + const [selectedIndexes, setSelectedIndexes] = useState(initialSelectedIndexes); + return ( + + {props.appMonitorsDtos.map((appMonitorDto, index) => ( + + + + + + + + + ))} + + ); + + function buildOnItemClick(index: number) { + return () => { + const indexOfSelectedIndex = selectedIndexes.indexOf(index); + if (indexOfSelectedIndex === -1) { + setSelectedIndexes([...selectedIndexes, index]); + } else { + setSelectedIndexes( + selectedIndexes.filter((selectedIndex) => selectedIndex !== index), + ); + } + }; + } +} + +function computeListItemPrimaryText(appMonitorDto: appMonitorDtoType) { + const frequencyText = variabilize(locale.importFrom.uptimeRobot.list.frequencyLabel, { + frequency: appMonitorDto.frequency, + }); + return `${appMonitorDto.displayName} (${frequencyText})`; +} + +export { AppMonitorList }; diff --git a/src/client/src/pages/ImportFrom/ImportFromUptimeRobot.tsx b/src/client/src/pages/ImportFrom/ImportFromUptimeRobot.tsx index 22f0e4c..b0c5e78 100644 --- a/src/client/src/pages/ImportFrom/ImportFromUptimeRobot.tsx +++ b/src/client/src/pages/ImportFrom/ImportFromUptimeRobot.tsx @@ -1,21 +1,46 @@ import { TextField } from '@mui/material'; -import { useState } from 'react'; +import { FormEvent, useState } from 'react'; import { Button } from '../../components/Button'; import { locale } from '../../locale'; +import { useApiCall } from '../../lib/useApiCall'; +import { appMonitorDtoType, monitorsApi } from '../../lib/api/monitorsApi'; +import { AppMonitorList } from './AppMonitorList'; function ImportFromUptimeRobot() { const [apiKey, setApiKey] = useState(''); + const [appMonitorsDtos, setAppMonitorDtos] = useState(); + const fetchMonitorsFromUptimeRobotApiCall = useApiCall({ + apiCall: monitorsApi.fetchMonitorsFromUptimeRobot, + onSuccess: (data) => { + setAppMonitorDtos(data); + }, + }); return (
- setApiKey(event.target.value)} - /> - +
+ setApiKey(event.target.value)} + /> + + + {appMonitorsDtos !== undefined && }
); + + function fetchMonitors(event: FormEvent) { + event.preventDefault(); + + fetchMonitorsFromUptimeRobotApiCall.perform(apiKey); + } } export { ImportFromUptimeRobot }; diff --git a/src/lib/externalApis/uptimeRobotApi.ts b/src/lib/externalApis/uptimeRobotApi.ts new file mode 100644 index 0000000..77df51a --- /dev/null +++ b/src/lib/externalApis/uptimeRobotApi.ts @@ -0,0 +1,30 @@ +type uptimeRobotMonitorApiType = { + id: number; + friendly_name: string; + url: string; + interval: number; +}; + +function buildUptimeRobotApi(uptimeRobotApiKey: string) { + const BASE_URL = `https://api.uptimerobot.com/v2`; + + return { fetchMonitors }; + + async function fetchMonitors() { + const URL = `${BASE_URL}/getMonitors?format=json&api_key=${uptimeRobotApiKey}`; + try { + const response = await fetch(URL, { method: 'POST' }); + const data = (await response.json()) as { monitors: uptimeRobotMonitorApiType[] }; + + return data.monitors.map((monitor) => ({ + url: monitor.url, + frequency: monitor.interval / 60, + displayName: monitor.friendly_name, + })); + } catch (error) { + throw new Error(`Le serveur uptimeRobot a renvoyé l'erreur suivante : ${error}`); + } + } +} + +export { buildUptimeRobotApi }; diff --git a/src/modules/monitor/monitor.controller.ts b/src/modules/monitor/monitor.controller.ts index 2abd179..551c5ec 100644 --- a/src/modules/monitor/monitor.controller.ts +++ b/src/modules/monitor/monitor.controller.ts @@ -12,6 +12,7 @@ function buildMonitorController() { getMyMonitorSummary, pingCronMonitor, createMonitor, + fetchMonitorsFromUptimeRobot, }; return monitorController; @@ -20,6 +21,10 @@ function buildMonitorController() { return monitorService.assertIsMonitorUpByName(params.urlParams.monitorName); } + async function fetchMonitorsFromUptimeRobot(params: { body: { uptimeRobotApiKey: string } }) { + return monitorService.fetchMonitorsFromUptimeRobot(params.body.uptimeRobotApiKey); + } + async function getMyMonitorSummary(params: { urlParams: { monitorId: string } }) { return monitorService.getMyMonitorSummary(params.urlParams.monitorId); } diff --git a/src/modules/monitor/monitor.service.ts b/src/modules/monitor/monitor.service.ts index a6900de..1e6df30 100644 --- a/src/modules/monitor/monitor.service.ts +++ b/src/modules/monitor/monitor.service.ts @@ -1,4 +1,5 @@ import { dataSource } from '../../dataSource'; +import { buildUptimeRobotApi } from '../../lib/externalApis/uptimeRobotApi'; import { slugify } from '../../lib/utils'; import { buildMonitorEventService } from '../monitorEvent'; import { eventKindType } from '../monitorEvent/types'; @@ -20,6 +21,7 @@ function buildMonitorService() { pingAppMonitor, checkAllCronMonitors, pingCronMonitor, + fetchMonitorsFromUptimeRobot, }; return monitorService; @@ -224,19 +226,9 @@ function buildMonitorService() { } } - async function getMyCronMonitorSummary(monitorId: Monitor['id']) { - const monitorEventService = buildMonitorEventService(); + async function fetchMonitorsFromUptimeRobot(uptimeRobotApiKey: string) { + const uptimeRobotApi = buildUptimeRobotApi(uptimeRobotApiKey); - const cronMonitor = (await monitorRepository.findOneByOrFail({ - id: monitorId, - kind: 'cron', - })) as cronMonitorType; - const monitorEvents = await monitorEventService.getEventsForMonitor(monitorId); - const status = await getCronMonitorStatus(cronMonitor); - return { - name: cronMonitor.name, - status, - events: monitorEvents, - }; + return uptimeRobotApi.fetchMonitors(); } } diff --git a/src/modules/user/user.service.ts b/src/modules/user/user.service.ts index 370a70f..ef62f36 100644 --- a/src/modules/user/user.service.ts +++ b/src/modules/user/user.service.ts @@ -46,7 +46,7 @@ function buildUserService() { const user = await userRepository.findOneOrFail({ where: { email } }); const isPasswordCorrect = hasher.verify(password, user.hashedPassword); - console.log(hasher.hash(password)); + if (isPasswordCorrect) { const token = createJwt({ userId: user.id, email: user.email }); const userInfo = { email }; diff --git a/src/router/monitorRoutes.ts b/src/router/monitorRoutes.ts index c4ac079..5e25af9 100644 --- a/src/router/monitorRoutes.ts +++ b/src/router/monitorRoutes.ts @@ -40,6 +40,12 @@ const monitorRoutes: Array> = [ path: '/monitors/:monitorName/health', controller: monitorController.assertIsMonitorUpByName, }, + { + method: 'POST', + kind: 'authenticated', + path: '/uptime-robot/monitors', + controller: monitorController.fetchMonitorsFromUptimeRobot, + }, ]; export { monitorRoutes };