Skip to content
This repository has been archived by the owner on Oct 13, 2024. It is now read-only.

Commit

Permalink
Merge pull request #443 from Swetrix/feature/uptime-monitoring
Browse files Browse the repository at this point in the history
(feature) Uptime monitoring
  • Loading branch information
Blaumaus authored Aug 26, 2024
2 parents 5f70842 + b5d0a07 commit 21a0dd4
Show file tree
Hide file tree
Showing 36 changed files with 3,026 additions and 1,463 deletions.
1 change: 1 addition & 0 deletions app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ const App: React.FC<IApp> = ({ ssrTheme, ssrAuthenticated }) => {
!_endsWith(pathname, '/subscribers/invite') &&
!_endsWith(pathname, '/subscribers/invite') &&
!_includes(pathname, '/alerts/') &&
!_includes(pathname, '/uptime/') &&
!_includes(pathname, '/settings/')

const routesWithOutHeader = [
Expand Down
117 changes: 116 additions & 1 deletion app/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ import { authActions } from 'redux/reducers/auth'
import sagaActions from 'redux/sagas/actions'
import { getAccessToken, removeAccessToken, setAccessToken } from 'utils/accessToken'
import { getRefreshToken, removeRefreshToken } from 'utils/refreshToken'
import { DEFAULT_ALERTS_TAKE, API_URL } from 'redux/constants'
import { DEFAULT_ALERTS_TAKE, API_URL, DEFAULT_MONITORS_TAKE } from 'redux/constants'
import { IUser } from 'redux/models/IUser'
import { IAuth } from 'redux/models/IAuth'
import { IProject, IOverall, IProjectNames } from 'redux/models/IProject'
import { IAlerts } from 'redux/models/IAlerts'
import { ISharedProject } from 'redux/models/ISharedProject'
import { ISubscribers } from 'redux/models/ISubscribers'
import { IFilter, IProjectViewCustomEvent } from 'pages/Project/View/interfaces/traffic'
import { Monitor, MonitorOverall } from 'redux/models/Uptime'

const debug = Debug('swetrix:api')

Expand Down Expand Up @@ -984,6 +985,120 @@ export const deleteAlert = (id: string) =>
throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message
})

export interface ICreateMonitor extends Omit<Monitor, 'id' | 'createdAt' | 'updatedAt'> {}

export const getAllMonitors = (take: number = DEFAULT_MONITORS_TAKE, skip: number = 0) =>
api
.get(`project/monitors?take=${take}&skip=${skip}`)
.then(
(
response,
): {
results: Monitor[]
total: number
page_total: number
} => response.data,
)
.catch((error) => {
debug('%s', error)
throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message
})

export const getProjectMonitors = (projectId: string, take: number = DEFAULT_MONITORS_TAKE, skip: number = 0) =>
api
.get(`project/${projectId}/monitors?take=${take}&skip=${skip}`)
.then(
(
response,
): {
results: Monitor[]
total: number
page_total: number
} => response.data,
)
.catch((error) => {
debug('%s', error)
throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message
})

export const getMonitorOverallStats = (
pid: string,
monitorIds: string[],
period: string,
from = '',
to = '',
timezone = 'Etc/GMT',
password?: string,
) =>
api
.get(
`log/monitor-data/birdseye?pid=${pid}&monitorIds=[${_map(monitorIds, (pid) => `"${pid}"`).join(
',',
)}]&period=${period}&from=${from}&to=${to}&timezone=${timezone}`,
{
headers: {
'x-password': password,
},
},
)
.then((response): MonitorOverall => response.data)
.catch((error) => {
debug('%s', error)
throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message
})

export const getMonitorStats = (
pid: string,
monitorId: string,
period: string = '1d',
timeBucket: string = 'hour',
from: string = '',
to: string = '',
timezone: string = '',
password: string | undefined = '',
) =>
api
.get(
`log/monitor-data?pid=${pid}&monitorId=${monitorId}&timeBucket=${timeBucket}&period=${period}&from=${from}&to=${to}&timezone=${timezone}`,
{
headers: {
'x-password': password,
},
},
)
.then((response) => response.data)
.catch((error) => {
debug('%s', error)
throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message
})

export const createMonitor = (pid: string, data: ICreateMonitor) =>
api
.post(`project/${pid}/monitor`, data)
.then((response): Monitor => response.data)
.catch((error) => {
debug('%s', error)
throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message
})

export const updateMonitor = (pid: string, id: string, data: Partial<Monitor>) =>
api
.patch(`project/${pid}/monitor/${id}`, data)
.then((response): Monitor => response.data)
.catch((error) => {
debug('%s', error)
throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message
})

export const deleteMonitor = (pid: string, id: string) =>
api
.delete(`project/${pid}/monitor/${id}`)
.then((response) => response.data)
.catch((error) => {
debug('%s', error)
throw _isEmpty(error.response.data?.message) ? error.response.data : error.response.data.message
})

export const reGenerateCaptchaSecretKey = (pid: string) =>
api
.post(`project/secret-gen/${pid}`)
Expand Down
3 changes: 3 additions & 0 deletions app/hooks/useRequiredParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { useParams } from '@remix-run/react'

export const useRequiredParams = <T extends Record<string, unknown>>() => useParams() as T
5 changes: 4 additions & 1 deletion app/hooks/useSize.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* eslint-disable consistent-return */
import type { MutableRefObject } from 'react'
import { useEffect, useRef, useState } from 'react'
import _get from 'lodash/get'

export default () => {
const useSize = (): [MutableRefObject<null>, { width: number; height: number }] => {
const [size, setSize] = useState({
width: 0,
height: 0,
Expand All @@ -28,3 +29,5 @@ export default () => {

return [ref, size]
}

export default useSize
1 change: 0 additions & 1 deletion app/pages/Project/Alerts/View/ProjectAlertsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ const ProjectAlerts = ({ projectId }: IProjectAlerts): JSX.Element => {
const [isPaidFeatureOpened, setIsPaidFeatureOpened] = useState<boolean>(false)
const navigate = useNavigate()

// @ts-ignore
const limits = PLAN_LIMITS[user?.planCode] || PLAN_LIMITS.trial
const isLimitReached = authenticated && total >= limits?.maxAlerts

Expand Down
135 changes: 132 additions & 3 deletions app/pages/Project/View/ViewProject.helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,8 @@ const getColumns = (
[key: string]: string[]
},
) => {
const { views, bounce, viewsPerUnique, unique, trendlines, sessionDuration, occurrences } = activeChartMetrics
const { views, bounce, viewsPerUnique, unique, trendlines, sessionDuration, occurrences, avgResponseTime } =
activeChartMetrics

const columns: any[] = [['x', ..._map(chart.x, (el) => dayjs(el).toDate())]]

Expand All @@ -255,6 +256,10 @@ const getColumns = (
}
}

if (avgResponseTime) {
columns.push(['avgResponseTime', ...chart.avgResponseTime])
}

if (views) {
columns.push(['total', ...chart.visits])
if (trendlines) {
Expand Down Expand Up @@ -939,7 +944,6 @@ const getSettingsSession = (
}
}

// function to get the settings and data for the error details chart
const getSettingsError = (
chart: any,
timeBucket: string,
Expand Down Expand Up @@ -1065,7 +1069,6 @@ const getSettingsError = (
}
}

// function to get the settings and data for the funnels chart
const getSettingsFunnels = (funnel: IAnalyticsFunnel[], totalPageviews: number, t: typeof i18next.t): ChartOptions => {
const values = _map(funnel, (step) => {
if (_startsWith(step.value, '/')) {
Expand Down Expand Up @@ -1208,6 +1211,131 @@ const getSettingsFunnels = (funnel: IAnalyticsFunnel[], totalPageviews: number,
}
}

const getSettingsUptime = (
chart: any,
timeBucket: string,
timeFormat: string,
rotateXAxis: boolean,
chartType: string,
): ChartOptions => {
const xAxisSize = _size(chart.x)

const columns = getColumns(chart, { avgResponseTime: true }, {})

let regionStart

if (xAxisSize > 1) {
regionStart = dayjs(chart.x[xAxisSize - 2]).toDate()
} else {
regionStart = dayjs(chart.x[xAxisSize - 1]).toDate()
}

return {
data: {
x: 'x',
columns,
types: {
avgResponseTime: chartType === chartTypes.line ? area() : bar(),
},
colors: {
avgResponseTime: '#709775',
},
regions: {
avgResponseTime: [
{
// @ts-expect-error
start: regionStart,
style: {
dasharray: '6 2',
},
},
],
},
},
transition: {
duration: 500,
},
resize: {
auto: true,
timer: false,
},
axis: {
x: {
clipPath: false,
tick: {
fit: true,
rotate: rotateXAxis ? 45 : 0,
// @ts-expect-error
format:
timeFormat === TimeFormat['24-hour']
? (x: string) => d3.timeFormat(tbsFormatMapper24h[timeBucket])(x)
: (x: string) => d3.timeFormat(tbsFormatMapper[timeBucket])(x),
},
localtime: timeFormat === TimeFormat['24-hour'],
type: 'timeseries',
},
y: {
tick: {
// @ts-expect-error
format: (d: string) => getStringFromTime(getTimeFromSeconds(d), true),
},
show: true,
inner: true,
},
},
tooltip: {
contents: (item: any, _: any, __: any, color: any) => {
return `<ul class='bg-gray-100 dark:text-gray-50 dark:bg-slate-800 rounded-md shadow-md px-3 py-1'>
<li class='font-semibold'>${
timeFormat === TimeFormat['24-hour']
? d3.timeFormat(tbsFormatMapperTooltip24h[timeBucket])(item[0].x)
: d3.timeFormat(tbsFormatMapperTooltip[timeBucket])(item[0].x)
}</li>
<hr class='border-gray-200 dark:border-gray-600' />
${_map(
item,
(el: { id: string; index: number; name: string; value: string; x: Date }) => `
<li class='flex justify-between'>
<div class='flex justify-items-start'>
<div class='w-3 h-3 rounded-sm mt-1.5 mr-2' style=background-color:${color(el.id)}></div>
<span>${el.name}</span>
</div>
<span class='pl-4'>${getStringFromTime(getTimeFromSeconds(el.value), true)}</span>
</li>
`,
).join('')}`
},
},
point:
chartType === chartTypes.bar
? {}
: {
focus: {
only: xAxisSize > 1,
},
pattern: ['circle'],
r: 2,
},
legend: {
item: {
tile: {
type: 'circle',
width: 10,
r: 3,
},
},
hide: ['uniqueCompare', 'totalCompare', 'bounceCompare', 'sessionDurationCompare'],
},
area: {
linearGradient: true,
},
bar: {
linearGradient: true,
},
bindto: '#avgResponseUptimeChart',
}
}

const perfomanceChartCompare = [
'dnsCompare',
'tlsCompare',
Expand Down Expand Up @@ -1636,4 +1764,5 @@ export {
CHART_MEASURES_MAPPING_PERF,
getSettingsError,
ERROR_FILTERS_MAPPING,
getSettingsUptime,
}
Loading

0 comments on commit 21a0dd4

Please sign in to comment.