Openstatus www.openstatus.dev

πŸš€ autofix ci (#939)

* πŸš€ autofix ci

* ci: apply automated fixes

* πŸš€lint

* ci: apply automated fixes

* πŸš€ lint

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

authored by

Thibault Le Ouay
autofix-ci[bot]
and committed by
GitHub
102d793a 22c13a09

+457 -432
+15 -11
.github/workflows/lint.yml
··· 1 1 # https://github.com/kentcdodds/kentcdodds.com/blob/main/.github/workflows/deployment.yml 2 - name: Code Check 2 + name: autofix.ci # needed to securely identify the workflow 3 3 on: 4 - push: 5 - branches: 6 - - "main" 7 4 pull_request: 8 - branches: [main] 5 + 6 + concurrency: 7 + group: ${{ github.workflow }}-${{ github.event.number || github.ref }} 8 + cancel-in-progress: true 9 + 10 + permissions: 11 + contents: read 12 + 9 13 10 14 jobs: 11 - lint: 12 - name: ⬣ BiomeJS 15 + autofix: 16 + name: autofix 13 17 runs-on: ubuntu-latest 14 - timeout-minutes: 15 15 - env: 16 - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} 17 - TURBO_TEAM: ${{ secrets.TURBO_TEAM }} 18 18 steps: 19 19 - name: ⬇️ Checkout repo 20 20 uses: actions/checkout@v4 ··· 35 35 36 36 - name: πŸ”¬ Lint 37 37 run: pnpm format 38 + - name: Apply fixes 39 + uses: autofix-ci/action@dd55f44df8f7cdb7a6bf74c78677eb8acd40cd0a 40 + with: 41 + commit-message: 'ci: apply automated fixes'
+1 -1
.vscode/settings.json
··· 1 1 { 2 2 "editor.codeActionsOnSave": {}, 3 3 "editor.defaultFormatter": "biomejs.biome", 4 - "editor.formatOnSave": true, 4 + "editor.formatOnSave": false, 5 5 "typescript.tsdk": "node_modules/typescript/lib", 6 6 7 7 "typescript.enablePromptUseWorkspaceTsdk": true,
+4 -4
apps/server/src/checker/alerting.ts
··· 5 5 selectNotificationSchema, 6 6 } from "@openstatus/db/src/schema"; 7 7 8 + import type { MonitorFlyRegion } from "@openstatus/db/src/schema/constants"; 8 9 import { checkerAudit } from "../utils/audit-log"; 9 10 import { providerToFunction } from "./utils"; 10 - import type { MonitorFlyRegion } from "@openstatus/db/src/schema/constants"; 11 11 12 12 export const triggerNotifications = async ({ 13 13 monitorId, ··· 28 28 .from(schema.notificationsToMonitors) 29 29 .innerJoin( 30 30 schema.notification, 31 - eq(schema.notification.id, schema.notificationsToMonitors.notificationId) 31 + eq(schema.notification.id, schema.notificationsToMonitors.notificationId), 32 32 ) 33 33 .innerJoin( 34 34 schema.monitor, 35 - eq(schema.monitor.id, schema.notificationsToMonitors.monitorId) 35 + eq(schema.monitor.id, schema.notificationsToMonitors.monitorId), 36 36 ) 37 37 .where(eq(schema.monitor.id, Number(monitorId))) 38 38 .all(); 39 39 for (const notif of notifications) { 40 40 console.log( 41 - `πŸ’Œ sending notification for ${monitorId} and chanel ${notif.notification.provider} for ${notifType}` 41 + `πŸ’Œ sending notification for ${monitorId} and chanel ${notif.notification.provider} for ${notifType}`, 42 42 ); 43 43 const monitor = selectMonitorSchema.parse(notif.monitor); 44 44 switch (notifType) {
+9 -9
apps/server/src/checker/index.ts
··· 10 10 } from "@openstatus/db/src/schema/monitors/validation"; 11 11 import { Redis } from "@openstatus/upstash"; 12 12 13 + import { flyRegions } from "@openstatus/db/src/schema/constants"; 13 14 import { env } from "../env"; 14 15 import { checkerAudit } from "../utils/audit-log"; 15 16 import { triggerNotifications, upsertMonitorStatus } from "./alerting"; 16 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 17 17 18 18 export const checkerRoute = new Hono(); 19 19 const redis = Redis.fromEnv(); ··· 59 59 and( 60 60 eq(incidentTable.monitorId, Number(monitorId)), 61 61 isNull(incidentTable.resolvedAt), 62 - isNull(incidentTable.acknowledgedAt) 63 - ) 62 + isNull(incidentTable.acknowledgedAt), 63 + ), 64 64 ) 65 65 .get(); 66 66 ··· 141 141 const numberOfRegions = monitor.regions.length; 142 142 143 143 console.log( 144 - `πŸ€“ MonitorID ${monitorId} incident current affected ${nbAffectedRegion} total region ${numberOfRegions}` 144 + `πŸ€“ MonitorID ${monitorId} incident current affected ${nbAffectedRegion} total region ${numberOfRegions}`, 145 145 ); 146 146 // If the number of affected regions is greater than half of the total region, we trigger the alerting 147 147 // 4 of 6 monitor need to fail to trigger an alerting ··· 155 155 eq(incidentTable.monitorId, Number(monitorId)), 156 156 isNull(incidentTable.resolvedAt), 157 157 isNull(incidentTable.acknowledgedAt), 158 - eq(incidentTable.startedAt, new Date(cronTimestamp)) 159 - ) 158 + eq(incidentTable.startedAt, new Date(cronTimestamp)), 159 + ), 160 160 ) 161 161 .get(); 162 162 ··· 233 233 const numberOfRegions = monitor.regions.length; 234 234 235 235 console.log( 236 - `πŸ€“ MonitorId ${monitorId} recovering incident current ${nbAffectedRegion} total region ${numberOfRegions}` 236 + `πŸ€“ MonitorId ${monitorId} recovering incident current ${nbAffectedRegion} total region ${numberOfRegions}`, 237 237 ); 238 238 // // If the number of affected regions is greater than half of the total region, we trigger the alerting 239 239 // // 4 of 6 monitor need to fail to trigger an alerting ··· 245 245 and( 246 246 eq(incidentTable.monitorId, Number(monitorId)), 247 247 isNull(incidentTable.resolvedAt), 248 - isNull(incidentTable.acknowledgedAt) 249 - ) 248 + isNull(incidentTable.acknowledgedAt), 249 + ), 250 250 ) 251 251 .get(); 252 252 if (incident) {
+6 -6
apps/server/src/checker/utils.ts
··· 5 5 } from "@openstatus/db/src/schema"; 6 6 import { 7 7 sendAlert as sendDiscordAlert, 8 - sendRecovery as sendDiscordRecovery, 9 8 sendDegraded as sendDiscordDegraded, 9 + sendRecovery as sendDiscordRecovery, 10 10 } from "@openstatus/notification-discord"; 11 11 import { 12 12 sendAlert as sendEmailAlert, 13 - sendRecovery as sendEmailRecovery, 14 13 sendDegraded as sendEmailDegraded, 14 + sendRecovery as sendEmailRecovery, 15 15 } from "@openstatus/notification-emails"; 16 16 import { 17 17 sendAlert as sendSlackAlert, 18 - sendRecovery as sendSlackRecovery, 19 18 sendDegraded as sendSlackDegraded, 19 + sendRecovery as sendSlackRecovery, 20 20 } from "@openstatus/notification-slack"; 21 21 import { 22 22 sendAlert as sendSmsAlert, 23 - sendRecovery as sendSmsRecovery, 24 23 sendDegraded as sendSmsDegraded, 24 + sendRecovery as sendSmsRecovery, 25 25 } from "@openstatus/notification-twillio-sms"; 26 26 27 27 import { 28 - sendAlert as sendPagerdutyAlert, 28 + sendDegraded as sendPagerDutyDegraded, 29 29 sendRecovery as sendPagerDutyRecovery, 30 - sendDegraded as sendPagerDutyDegraded, 30 + sendAlert as sendPagerdutyAlert, 31 31 } from "@openstatus/notification-pagerduty"; 32 32 33 33 type SendNotification = ({
+9 -9
apps/server/src/v1/check/post.ts
··· 107 107 if (aggregated) { 108 108 // This is ugly 109 109 const dnsArray = fulfilledRequest.map( 110 - (r) => r.timing.dnsDone - r.timing.dnsStart 110 + (r) => r.timing.dnsDone - r.timing.dnsStart, 111 111 ); 112 112 const connectArray = fulfilledRequest.map( 113 - (r) => r.timing.connectDone - r.timing.connectStart 113 + (r) => r.timing.connectDone - r.timing.connectStart, 114 114 ); 115 115 const tlsArray = fulfilledRequest.map( 116 - (r) => r.timing.tlsHandshakeDone - r.timing.tlsHandshakeStart 116 + (r) => r.timing.tlsHandshakeDone - r.timing.tlsHandshakeStart, 117 117 ); 118 118 const firstArray = fulfilledRequest.map( 119 - (r) => r.timing.firstByteDone - r.timing.firstByteStart 119 + (r) => r.timing.firstByteDone - r.timing.firstByteStart, 120 120 ); 121 121 const transferArray = fulfilledRequest.map( 122 - (r) => r.timing.transferDone - r.timing.transferStart 122 + (r) => r.timing.transferDone - r.timing.transferStart, 123 123 ); 124 124 const latencyArray = fulfilledRequest.map((r) => r.latency); 125 125 126 126 const dnsPercentile = percentile([50, 75, 95, 99], dnsArray) as number[]; 127 127 const connectPercentile = percentile( 128 128 [50, 75, 95, 99], 129 - connectArray 129 + connectArray, 130 130 ) as number[]; 131 131 const tlsPercentile = percentile([50, 75, 95, 99], tlsArray) as number[]; 132 132 const firstPercentile = percentile( 133 133 [50, 75, 95, 99], 134 - firstArray 134 + firstArray, 135 135 ) as number[]; 136 136 137 137 const transferPercentile = percentile( 138 138 [50, 75, 95, 99], 139 - transferArray 139 + transferArray, 140 140 ) as number[]; 141 141 const latencyPercentile = percentile( 142 142 [50, 75, 95, 99], 143 - latencyArray 143 + latencyArray, 144 144 ) as number[]; 145 145 146 146 const aggregatedDNS = AggregatedResponseSchema.parse({
+2 -2
apps/server/src/v1/incidents/get.ts
··· 40 40 .where( 41 41 and( 42 42 eq(incidentTable.workspaceId, Number(workspaceId)), 43 - eq(incidentTable.id, Number(id)) 44 - ) 43 + eq(incidentTable.id, Number(id)), 44 + ), 45 45 ) 46 46 .get(); 47 47
+2 -2
apps/server/src/v1/incidents/put.ts
··· 59 59 .where( 60 60 and( 61 61 eq(incidentTable.id, Number(id)), 62 - eq(incidentTable.workspaceId, Number(workspaceId)) 63 - ) 62 + eq(incidentTable.workspaceId, Number(workspaceId)), 63 + ), 64 64 ) 65 65 .get(); 66 66
+3 -3
apps/server/src/v1/index.ts
··· 1 1 import { OpenAPIHono } from "@hono/zod-openapi"; 2 + import { apiReference } from "@scalar/hono-api-reference"; 2 3 import { cors } from "hono/cors"; 3 4 import { logger } from "hono/logger"; 4 - import { apiReference } from "@scalar/hono-api-reference"; 5 5 6 + import type { Limits } from "@openstatus/db/src/schema/plan/schema"; 6 7 import { handleError, handleZodError } from "../libs/errors"; 7 8 import { checkAPI } from "./check"; 8 9 import { incidentsApi } from "./incidents"; ··· 13 14 import { pagesApi } from "./pages"; 14 15 import { statusReportUpdatesApi } from "./statusReportUpdates"; 15 16 import { statusReportsApi } from "./statusReports"; 16 - import type { Limits } from "@openstatus/db/src/schema/plan/schema"; 17 17 18 18 export type Variables = { 19 19 workspaceId: string; ··· 47 47 spec: { 48 48 url: "/v1/openapi", 49 49 }, 50 - }) 50 + }), 51 51 ); 52 52 /** 53 53 * Authentification Middleware
+2 -2
apps/server/src/v1/middleware.ts
··· 4 4 import { db, eq } from "@openstatus/db"; 5 5 import { selectWorkspaceSchema, workspace } from "@openstatus/db/src/schema"; 6 6 import { getPlanConfig } from "@openstatus/db/src/schema/plan/utils"; 7 - import type { Variables } from "./index"; 8 7 import { HTTPException } from "hono/http-exception"; 8 + import type { Variables } from "./index"; 9 9 10 10 export async function middleware( 11 11 c: Context<{ Variables: Variables }, "/*">, 12 - next: Next 12 + next: Next, 13 13 ) { 14 14 const key = c.req.header("x-openstatus-key"); 15 15 if (!key) throw new HTTPException(401, { message: "Unauthorized" });
+2 -2
apps/server/src/v1/monitors/get.ts
··· 41 41 and( 42 42 eq(monitor.id, Number(id)), 43 43 eq(monitor.workspaceId, Number(workspaceId)), 44 - isNull(monitor.deletedAt) 45 - ) 44 + isNull(monitor.deletedAt), 45 + ), 46 46 ) 47 47 .get(); 48 48
+2 -2
apps/server/src/v1/monitors/get_all.ts
··· 37 37 .where( 38 38 and( 39 39 eq(monitor.workspaceId, Number(workspaceId)), 40 - isNull(monitor.deletedAt) 41 - ) 40 + isNull(monitor.deletedAt), 41 + ), 42 42 ) 43 43 .all(); 44 44
+3 -3
apps/server/src/v1/monitors/post.ts
··· 7 7 import { HTTPException } from "hono/http-exception"; 8 8 import { serialize } from "../../../../../packages/assertions/src"; 9 9 10 + import { getLimit } from "@openstatus/db/src/schema/plan/utils"; 10 11 import { env } from "../../env"; 11 12 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 12 13 import type { monitorsApi } from "./index"; 13 14 import { MonitorSchema } from "./schema"; 14 15 import { getAssertions } from "./utils"; 15 - import { getLimit } from "@openstatus/db/src/schema/plan/utils"; 16 16 17 17 const postRoute = createRoute({ 18 18 method: "post", ··· 54 54 .where( 55 55 and( 56 56 eq(monitor.workspaceId, Number(workspaceId)), 57 - isNull(monitor.deletedAt) 58 - ) 57 + isNull(monitor.deletedAt), 58 + ), 59 59 ) 60 60 .all() 61 61 )[0].count;
+6 -6
apps/server/src/v1/monitors/schema.ts
··· 1 1 import { z } from "@hono/zod-openapi"; 2 2 3 3 import { monitorMethods } from "@openstatus/db/src/schema"; 4 + import { 5 + flyRegions, 6 + monitorPeriodicitySchema, 7 + } from "@openstatus/db/src/schema/constants"; 4 8 import { ZodError } from "zod"; 5 9 import { 6 10 numberCompare, 7 11 stringCompare, 8 12 } from "../../../../../packages/assertions/src/v1"; 9 - import { 10 - flyRegions, 11 - monitorPeriodicitySchema, 12 - } from "@openstatus/db/src/schema/constants"; 13 13 14 14 const statusAssertion = z 15 15 .object({ ··· 113 113 ]); 114 114 } 115 115 }, 116 - z.array(z.enum(flyRegions)) 116 + z.array(z.enum(flyRegions)), 117 117 ) 118 118 .default([]) 119 119 .openapi({ ··· 161 161 ]); 162 162 } 163 163 }, 164 - z.array(z.object({ key: z.string(), value: z.string() })).default([]) 164 + z.array(z.object({ key: z.string(), value: z.string() })).default([]), 165 165 ) 166 166 .nullish() 167 167 .openapi({
+3 -3
apps/server/src/v1/monitors/summary/get.ts
··· 65 65 and( 66 66 eq(monitor.id, Number(id)), 67 67 eq(monitor.workspaceId, Number(workspaceId)), 68 - isNull(monitor.deletedAt) 69 - ) 68 + isNull(monitor.deletedAt), 69 + ), 70 70 ) 71 71 .get(); 72 72 ··· 75 75 } 76 76 77 77 const cache = await redis.get<z.infer<typeof dailyStatsSchemaArray>>( 78 - `${id}-daily-stats` 78 + `${id}-daily-stats`, 79 79 ); 80 80 if (cache) { 81 81 console.log("fetching from cache");
+2 -2
apps/server/src/v1/notifications/get.ts
··· 43 43 .where( 44 44 and( 45 45 eq(page.workspaceId, Number(workspaceId)), 46 - eq(notification.id, Number(id)) 47 - ) 46 + eq(notification.id, Number(id)), 47 + ), 48 48 ) 49 49 .get(); 50 50
+3 -3
apps/server/src/v1/notifications/post.ts
··· 8 8 notificationsToMonitors, 9 9 selectNotificationSchema, 10 10 } from "@openstatus/db/src/schema"; 11 + import { getLimit } from "@openstatus/db/src/schema/plan/utils"; 11 12 import { HTTPException } from "hono/http-exception"; 12 13 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 13 14 import type { notificationsApi } from "./index"; 14 15 import { NotificationSchema } from "./schema"; 15 - import { getLimit } from "@openstatus/db/src/schema/plan/utils"; 16 16 17 17 const postRoute = createRoute({ 18 18 method: "post", ··· 77 77 and( 78 78 inArray(monitor.id, monitors), 79 79 eq(monitor.workspaceId, Number(workspaceId)), 80 - isNull(monitor.deletedAt) 81 - ) 80 + isNull(monitor.deletedAt), 81 + ), 82 82 ) 83 83 .all(); 84 84
+3 -3
apps/server/src/v1/pageSubscribers/post.ts
··· 49 49 .select() 50 50 .from(page) 51 51 .where( 52 - and(eq(page.id, Number(id)), eq(page.workspaceId, Number(workspaceId))) 52 + and(eq(page.id, Number(id)), eq(page.workspaceId, Number(workspaceId))), 53 53 ) 54 54 .get(); 55 55 ··· 63 63 .where( 64 64 and( 65 65 eq(pageSubscriber.email, input.email), 66 - eq(pageSubscriber.pageId, Number(id)) 67 - ) 66 + eq(pageSubscriber.pageId, Number(id)), 67 + ), 68 68 ) 69 69 .get(); 70 70
+1 -1
apps/server/src/v1/pages/get.ts
··· 38 38 .select() 39 39 .from(page) 40 40 .where( 41 - and(eq(page.workspaceId, Number(workspaceId)), eq(page.id, Number(id))) 41 + and(eq(page.workspaceId, Number(workspaceId)), eq(page.id, Number(id))), 42 42 ) 43 43 .get(); 44 44
+3 -3
apps/server/src/v1/pages/post.ts
··· 4 4 import { db } from "@openstatus/db/src/db"; 5 5 import { monitor, monitorsToPages, page } from "@openstatus/db/src/schema"; 6 6 7 + import { getLimit } from "@openstatus/db/src/schema/plan/utils"; 7 8 import { HTTPException } from "hono/http-exception"; 8 9 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 9 10 import { isNumberArray } from "../utils"; 10 11 import type { pagesApi } from "./index"; 11 12 import { PageSchema } from "./schema"; 12 - import { getLimit } from "@openstatus/db/src/schema/plan/utils"; 13 13 14 14 const postRoute = createRoute({ 15 15 method: "post", ··· 96 96 and( 97 97 inArray(monitor.id, monitorIds), 98 98 eq(monitor.workspaceId, Number(workspaceId)), 99 - isNull(monitor.deletedAt) 100 - ) 99 + isNull(monitor.deletedAt), 100 + ), 101 101 ) 102 102 .all(); 103 103
+5 -5
apps/server/src/v1/pages/put.ts
··· 59 59 .select() 60 60 .from(page) 61 61 .where( 62 - and(eq(page.id, Number(id)), eq(page.workspaceId, Number(workspaceId))) 62 + and(eq(page.id, Number(id)), eq(page.workspaceId, Number(workspaceId))), 63 63 ) 64 64 .get(); 65 65 ··· 98 98 and( 99 99 inArray(monitor.id, monitorIds), 100 100 eq(monitor.workspaceId, Number(workspaceId)), 101 - isNull(monitor.deletedAt) 102 - ) 101 + isNull(monitor.deletedAt), 102 + ), 103 103 ) 104 104 .all(); 105 105 ··· 132 132 .where( 133 133 and( 134 134 inArray(monitorsToPages.monitorId, removedMonitors), 135 - eq(monitorsToPages.pageId, newPage.id) 136 - ) 135 + eq(monitorsToPages.pageId, newPage.id), 136 + ), 137 137 ); 138 138 } 139 139
+4 -4
apps/server/src/v1/statusReportUpdates/get.ts
··· 30 30 }); 31 31 32 32 export function registerGetStatusReportUpdate( 33 - api: typeof statusReportUpdatesApi 33 + api: typeof statusReportUpdatesApi, 34 34 ) { 35 35 return api.openapi(getRoute, async (c) => { 36 36 const workspaceId = c.get("workspaceId"); ··· 43 43 statusReport, 44 44 and( 45 45 eq(statusReport.id, statusReportUpdate.statusReportId), 46 - eq(statusReport.workspaceId, Number(workspaceId)) 47 - ) 46 + eq(statusReport.workspaceId, Number(workspaceId)), 47 + ), 48 48 ) 49 49 .where(eq(statusReportUpdate.id, Number(id))) 50 50 .get(); ··· 54 54 } 55 55 56 56 const data = StatusReportUpdateSchema.parse( 57 - _statusReportJoin.status_report_update 57 + _statusReportJoin.status_report_update, 58 58 ); 59 59 60 60 return c.json(data, 200);
+6 -6
apps/server/src/v1/statusReportUpdates/post.ts
··· 43 43 }); 44 44 45 45 export function registerPostStatusReportUpdate( 46 - api: typeof statusReportUpdatesApi 46 + api: typeof statusReportUpdatesApi, 47 47 ) { 48 48 return api.openapi(createStatusUpdate, async (c) => { 49 49 const workspaceId = c.get("workspaceId"); ··· 56 56 .where( 57 57 and( 58 58 eq(statusReport.id, input.statusReportId), 59 - eq(statusReport.workspaceId, Number(workspaceId)) 60 - ) 59 + eq(statusReport.workspaceId, Number(workspaceId)), 60 + ), 61 61 ) 62 62 .get(); 63 63 ··· 87 87 .where( 88 88 and( 89 89 eq(pageSubscriber.pageId, _statusReport.pageId), 90 - isNotNull(pageSubscriber.acceptedAt) 91 - ) 90 + isNotNull(pageSubscriber.acceptedAt), 91 + ), 92 92 ) 93 93 .all(); 94 94 ··· 99 99 .get(); 100 100 if (pageInfo) { 101 101 const subscribersEmails = subscribers.map( 102 - (subscriber) => subscriber.email 102 + (subscriber) => subscriber.email, 103 103 ); 104 104 105 105 // TODO: verify if we leak any email data here
+2 -2
apps/server/src/v1/statusReports/delete.ts
··· 40 40 .where( 41 41 and( 42 42 eq(statusReport.id, Number(id)), 43 - eq(statusReport.workspaceId, Number(workspaceId)) 44 - ) 43 + eq(statusReport.workspaceId, Number(workspaceId)), 44 + ), 45 45 ) 46 46 .get(); 47 47
+1 -1
apps/server/src/v1/statusReports/get.ts
··· 41 41 }, 42 42 where: and( 43 43 eq(statusReport.workspaceId, Number(workspaceId)), 44 - eq(statusReport.id, Number(id)) 44 + eq(statusReport.id, Number(id)), 45 45 ), 46 46 }); 47 47
+1 -1
apps/server/src/v1/statusReports/get_all.ts
··· 48 48 ...r, 49 49 statusReportUpdateIds: r.statusReportUpdates.map((u) => u.id), 50 50 monitorIds: r.monitorsToStatusReports.map((m) => m.monitorId), 51 - })) 51 + })), 52 52 ); 53 53 54 54 return c.json(data, 200);
+9 -9
apps/server/src/v1/statusReports/post.ts
··· 10 10 statusReportUpdate, 11 11 } from "@openstatus/db/src/schema"; 12 12 13 + import { getLimit } from "@openstatus/db/src/schema/plan/utils"; 13 14 import { sendEmailHtml } from "@openstatus/emails"; 14 15 import { HTTPException } from "hono/http-exception"; 15 16 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 16 17 import { isoDate } from "../utils"; 17 18 import type { statusReportsApi } from "./index"; 18 19 import { StatusReportSchema } from "./schema"; 19 - import { getLimit } from "@openstatus/db/src/schema/plan/utils"; 20 20 21 21 const postRoute = createRoute({ 22 22 method: "post", ··· 72 72 and( 73 73 eq(monitor.workspaceId, Number(workspaceId)), 74 74 inArray(monitor.id, monitorIds), 75 - isNull(monitor.deletedAt) 76 - ) 75 + isNull(monitor.deletedAt), 76 + ), 77 77 ) 78 78 .all(); 79 79 ··· 89 89 .where( 90 90 and( 91 91 eq(page.workspaceId, Number(workspaceId)), 92 - eq(page.id, rest.pageId) 93 - ) 92 + eq(page.id, rest.pageId), 93 + ), 94 94 ) 95 95 .all(); 96 96 ··· 127 127 monitorId: id, 128 128 statusReportId: _newStatusReport.id, 129 129 }; 130 - }) 130 + }), 131 131 ) 132 132 .returning(); 133 133 } ··· 139 139 .where( 140 140 and( 141 141 eq(pageSubscriber.pageId, _newStatusReport.pageId), 142 - isNotNull(pageSubscriber.acceptedAt) 143 - ) 142 + isNotNull(pageSubscriber.acceptedAt), 143 + ), 144 144 ) 145 145 .all(); 146 146 const pageInfo = await db ··· 150 150 .get(); 151 151 if (pageInfo) { 152 152 const subscribersEmails = subscribers.map( 153 - (subscriber) => subscriber.email 153 + (subscriber) => subscriber.email, 154 154 ); 155 155 await sendEmailHtml({ 156 156 to: subscribersEmails,
+5 -5
apps/server/src/v1/statusReports/update/post.ts
··· 56 56 .where( 57 57 and( 58 58 eq(statusReport.id, Number(id)), 59 - eq(statusReport.workspaceId, Number(workspaceId)) 60 - ) 59 + eq(statusReport.workspaceId, Number(workspaceId)), 60 + ), 61 61 ) 62 62 .returning() 63 63 .get(); ··· 83 83 .where( 84 84 and( 85 85 eq(pageSubscriber.pageId, _statusReport.pageId), 86 - isNotNull(pageSubscriber.acceptedAt) 87 - ) 86 + isNotNull(pageSubscriber.acceptedAt), 87 + ), 88 88 ) 89 89 .all(); 90 90 const pageInfo = await db ··· 94 94 .get(); 95 95 if (pageInfo) { 96 96 const subscribersEmails = subscribers.map( 97 - (subscriber) => subscriber.email 97 + (subscriber) => subscriber.email, 98 98 ); 99 99 await sendEmailHtml({ 100 100 to: subscribersEmails,
+2 -2
apps/web/src/app/(content)/features/_components/hero.tsx
··· 11 11 <div 12 12 className={cn( 13 13 "mx-auto my-16 flex max-w-xl flex-col items-center gap-4", 14 - className 14 + className, 15 15 )} 16 16 {...props} 17 17 > 18 18 <h1 19 19 className={cn( 20 20 "text-center font-cal text-5xl leading-tight", 21 - "bg-gradient-to-tl from-0% from-[hsl(var(--muted))] to-30% to-[hsl(var(--foreground))] bg-clip-text text-transparent" 21 + "bg-gradient-to-tl from-0% from-[hsl(var(--muted))] to-30% to-[hsl(var(--foreground))] bg-clip-text text-transparent", 22 22 )} 23 23 > 24 24 {title}
+7 -7
apps/web/src/app/(content)/features/monitoring/page.tsx
··· 1 + import { 2 + defaultMetadata, 3 + ogMetadata, 4 + twitterMetadata, 5 + } from "@/app/shared-metadata"; 1 6 import { Mdx } from "@/components/content/mdx"; 2 7 import { Chart } from "@/components/monitor-charts/chart"; 3 8 import { RegionsPreset } from "@/components/monitor-dashboard/region-preset"; ··· 7 12 import type { Region } from "@openstatus/tinybird"; 8 13 import { Button } from "@openstatus/ui"; 9 14 import { allUnrelateds } from "contentlayer/generated"; 15 + import type { Metadata } from "next"; 10 16 import Link from "next/link"; 11 17 import { Suspense } from "react"; 12 18 import { AssertionsTimingFormExample } from "../_components/assertions-timing-form-example"; ··· 14 20 import { Hero } from "../_components/hero"; 15 21 import { InteractiveFeature } from "../_components/interactive-feature"; 16 22 import { mockChartData, mockResponseData } from "../mock"; 17 - import type { Metadata } from "next"; 18 - import { 19 - defaultMetadata, 20 - ogMetadata, 21 - twitterMetadata, 22 - } from "@/app/shared-metadata"; 23 23 24 24 const { description, subtitle } = marketingProductPagesConfig[0]; 25 25 const code = allUnrelateds.find( 26 - (unrelated) => unrelated.slug === "ci-cd-features-block" 26 + (unrelated) => unrelated.slug === "ci-cd-features-block", 27 27 ); 28 28 29 29 export const metadata: Metadata = {
+12 -5
apps/web/src/app/(content)/features/status-page/page.tsx
··· 1 + import { 2 + defaultMetadata, 3 + ogMetadata, 4 + twitterMetadata, 5 + } from "@/app/shared-metadata"; 1 6 import { PasswordFormSuspense } from "@/app/status-page/[domain]/_components/password-form"; 2 7 import { SubscribeButton } from "@/app/status-page/[domain]/_components/subscribe-button"; 3 8 import { MaintenanceBanner } from "@/components/status-page/maintenance-banner"; ··· 6 11 import { Tracker } from "@/components/tracker/tracker"; 7 12 import { marketingProductPagesConfig } from "@/config/pages"; 8 13 import { Button, InputWithAddons } from "@openstatus/ui"; 14 + import type { Metadata } from "next"; 9 15 import Link from "next/link"; 10 16 import { Banner } from "../_components/banner"; 11 17 import { Hero } from "../_components/hero"; 12 18 import { InteractiveFeature } from "../_components/interactive-feature"; 13 19 import { maintenanceData, mockTrackerData, statusReportData } from "../mock"; 14 - import type { Metadata } from "next"; 15 - import { defaultMetadata, ogMetadata, twitterMetadata } from "@/app/shared-metadata"; 16 20 17 21 const { description, subtitle } = marketingProductPagesConfig[1]; 18 22 19 23 export const metadata: Metadata = { 20 24 ...defaultMetadata, 21 25 title: "Status Page", 22 - description:'Easily report to your users with our public or private status page.', 26 + description: 27 + "Easily report to your users with our public or private status page.", 23 28 twitter: { 24 29 ...twitterMetadata, 25 30 title: "Status Page", 26 - description:'Easily report to your users with our public or private status page.', 31 + description: 32 + "Easily report to your users with our public or private status page.", 27 33 }, 28 34 openGraph: { 29 35 ...ogMetadata, 30 36 title: "Status Page", 31 - description:'Easily report to your users with our public or private status page.', 37 + description: 38 + "Easily report to your users with our public or private status page.", 32 39 }, 33 40 }; 34 41
+14 -11
apps/web/src/app/_components/input-search.tsx
··· 33 33 const searchparams = inputValue 34 34 .trim() 35 35 .split(" ") 36 - .reduce((prev, curr) => { 37 - const [name, value] = curr.split(":"); 38 - if (value && name && curr !== currentWord) { 39 - // TODO: support multiple value with value.split(",") 40 - prev[name] = value; 41 - } 42 - return prev; 43 - }, {} as Record<string, string>); 36 + .reduce( 37 + (prev, curr) => { 38 + const [name, value] = curr.split(":"); 39 + if (value && name && curr !== currentWord) { 40 + // TODO: support multiple value with value.split(",") 41 + prev[name] = value; 42 + } 43 + return prev; 44 + }, 45 + {} as Record<string, string>, 46 + ); 44 47 onSearch(searchparams); 45 48 }, [onSearch, inputValue, currentWord]); 46 49 ··· 61 64 status: (number | null)[]; 62 65 limit: number[]; 63 66 region: string[]; 64 - } 67 + }, 65 68 ), 66 - [events] 69 + [events], 67 70 ); 68 71 69 72 type SearchKey = keyof typeof search; ··· 135 138 const prefix = isStarting ? "" : " "; 136 139 const input = prev.replace( 137 140 `${prefix}${currentWord}`, 138 - `${prefix}${value}` 141 + `${prefix}${value}`, 139 142 ); 140 143 return `${input}:`; 141 144 });
+6 -6
apps/web/src/app/api/checker/cron/_cron.ts
··· 45 45 const parent = client.queuePath( 46 46 env.GCP_PROJECT_ID, 47 47 env.GCP_LOCATION, 48 - periodicity 48 + periodicity, 49 49 ); 50 50 51 51 const timestamp = Date.now(); ··· 54 54 .select({ id: maintenance.id }) 55 55 .from(maintenance) 56 56 .where( 57 - and(lte(maintenance.from, new Date()), gte(maintenance.to, new Date())) 57 + and(lte(maintenance.from, new Date()), gte(maintenance.to, new Date())), 58 58 ) 59 59 .as("currentMaintenance"); 60 60 ··· 63 63 .from(maintenancesToMonitors) 64 64 .innerJoin( 65 65 currentMaintenance, 66 - eq(maintenancesToMonitors.maintenanceId, currentMaintenance.id) 66 + eq(maintenancesToMonitors.maintenanceId, currentMaintenance.id), 67 67 ); 68 68 69 69 const result = await db ··· 73 73 and( 74 74 eq(monitor.periodicity, periodicity), 75 75 eq(monitor.active, true), 76 - notInArray(monitor.id, currentMaintenanceMonitors) 77 - ) 76 + notInArray(monitor.id, currentMaintenanceMonitors), 77 + ), 78 78 ) 79 79 .all(); 80 80 ··· 127 127 const failed = allRequests.filter((r) => r.status === "rejected").length; 128 128 129 129 console.log( 130 - `End cron for ${periodicity} with ${allResult.length} jobs with ${success} success and ${failed} failed` 130 + `End cron for ${periodicity} with ${allResult.length} jobs with ${success} success and ${failed} failed`, 131 131 ); 132 132 }; 133 133 // timestamp needs to be in ms
+1 -1
apps/web/src/app/api/webhook/stripe/route.ts
··· 16 16 const event = stripe.webhooks.constructEvent( 17 17 payload, 18 18 signature, 19 - env.STRIPE_WEBHOOK_SECRET_KEY 19 + env.STRIPE_WEBHOOK_SECRET_KEY, 20 20 ); 21 21 22 22 /**
+1 -1
apps/web/src/app/app/(auth)/login/layout.tsx
··· 24 24 className="rounded-full border border-border" 25 25 /> 26 26 </Link> 27 - <div className="flex w-full max-w-lg flex-1 flex-col justify-center gap-8 text-center mx-auto md:text-left md:mx-0"> 27 + <div className="mx-auto flex w-full max-w-lg flex-1 flex-col justify-center gap-8 text-center md:mx-0 md:text-left"> 28 28 <div className="mx-auto grid gap-3"> 29 29 <h1 className="font-cal text-3xl text-foreground"> 30 30 Open Source Monitoring Service
+2 -2
apps/web/src/app/app/[workspaceSlug]/(dashboard)/layout.tsx
··· 1 + import { InfoAlertDialog } from "@/components/dashboard/info-alert-dialog"; 1 2 import { AppHeader } from "@/components/layout/header/app-header"; 2 3 import { api } from "@/trpc/server"; 4 + import Link from "next/link"; 3 5 import { notFound } from "next/navigation"; 4 6 import type { ReactNode } from "react"; 5 7 import { WorkspaceClientCookie } from "../worskpace-client-cookie"; 6 - import { InfoAlertDialog } from "@/components/dashboard/info-alert-dialog"; 7 - import Link from "next/link"; 8 8 9 9 // TODO: make the container min-h-screen and the footer below! 10 10 export default async function AppLayout({
+17 -9
apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/(overview)/page.tsx
··· 29 29 if (v === "true") return true; 30 30 if (v === "false") return false; 31 31 return undefined; 32 - }) 32 + }), 33 33 ) 34 34 .optional(), 35 35 pageSize: z.coerce.number().optional().default(10), ··· 73 73 { 74 74 monitorId: String(monitor.id), 75 75 }, 76 - { cache: "no-store", revalidate: 0 } 76 + { cache: "no-store", revalidate: 0 }, 77 77 ); 78 78 79 79 const data = await tb.endpointStatusPeriod("7d")( 80 80 { 81 81 monitorId: String(monitor.id), 82 82 }, 83 - { cache: "no-store", revalidate: 0 } 83 + { cache: "no-store", revalidate: 0 }, 84 84 ); 85 85 86 86 const [current] = metrics?.sort((a, b) => 87 - (a.lastTimestamp || 0) - (b.lastTimestamp || 0) < 0 ? 1 : -1 87 + (a.lastTimestamp || 0) - (b.lastTimestamp || 0) < 0 ? 1 : -1, 88 88 ) || [undefined]; 89 89 90 90 const incidents = _incidents.filter( 91 - (incident) => incident.monitorId === monitor.id 91 + (incident) => incident.monitorId === monitor.id, 92 92 ); 93 93 94 94 const tags = monitor.monitorTagsToMonitors.map( 95 - ({ monitorTag }) => monitorTag 95 + ({ monitorTag }) => monitorTag, 96 96 ); 97 97 98 98 const maintenances = _maintenances.filter((maintenance) => 99 - maintenance.monitors.includes(monitor.id) 99 + maintenance.monitors.includes(monitor.id), 100 100 ); 101 101 102 - return { monitor, metrics: current, data, incidents, maintenances, tags, isLimitReached }; 103 - }) 102 + return { 103 + monitor, 104 + metrics: current, 105 + data, 106 + incidents, 107 + maintenances, 108 + tags, 109 + isLimitReached, 110 + }; 111 + }), 104 112 ); 105 113 106 114 return (
+2 -2
apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/data/_components/data-table-wrapper.tsx
··· 19 19 import { LoadingAnimation } from "@/components/loading-animation"; 20 20 import { ResponseDetailTabs } from "@/components/ping-response-analysis/response-detail-tabs"; 21 21 import { api } from "@/trpc/client"; 22 - import type { z } from "zod"; 23 22 import type { monitorFlyRegionSchema } from "@openstatus/db/src/schema/constants"; 23 + import type { z } from "zod"; 24 24 25 25 // EXAMPLE: get the type of the response of the endpoint 26 26 // biome-ignore lint/correctness/noUnusedVariables: <explanation> ··· 82 82 url: row.original.url, 83 83 region: row.original.region, 84 84 cronTimestamp: row.original.cronTimestamp || undefined, 85 - }) 85 + }), 86 86 ); 87 87 88 88 if (!data || data.length === 0) return <p>Something went wrong</p>;
+2 -2
apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/edit/page.tsx
··· 39 39 degradedAfter: monitor.degradedAfter ?? undefined, 40 40 pages: pages 41 41 .filter((page) => 42 - page.monitorsToPages.map(({ monitorId }) => monitorId).includes(id) 42 + page.monitorsToPages.map(({ monitorId }) => monitorId).includes(id), 43 43 ) 44 44 .map(({ id }) => id), 45 45 notifications: monitorNotifications?.map(({ id }) => id), 46 46 tags: tags 47 47 .filter((tag) => 48 - tag.monitor.map(({ monitorId }) => monitorId).includes(id) 48 + tag.monitor.map(({ monitorId }) => monitorId).includes(id), 49 49 ) 50 50 .map(({ id }) => id), 51 51 }}
+1 -1
apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/layout.tsx
··· 54 54 <span className="text-muted-foreground/50 text-xs">β€’</span> 55 55 <TagBadgeWithTooltip 56 56 tags={monitor.monitorTagsToMonitors.map( 57 - ({ monitorTag }) => monitorTag 57 + ({ monitorTag }) => monitorTag, 58 58 )} 59 59 /> 60 60 </>
+1 -1
apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/overview/page.tsx
··· 44 44 value 45 45 ?.trim() 46 46 ?.split(",") 47 - .filter((i) => flyRegions.includes(i as Region)) ?? [] 47 + .filter((i) => flyRegions.includes(i as Region)) ?? [], 48 48 ), 49 49 }); 50 50
+1 -1
apps/web/src/app/app/[workspaceSlug]/(dashboard)/notifications/new/pagerduty/page.tsx
··· 1 1 import { ProFeatureAlert } from "@/components/billing/pro-feature-alert"; 2 2 import { NotificationForm } from "@/components/forms/notification/form"; 3 3 import { api } from "@/trpc/server"; 4 - import { PagerDutySchema } from "@openstatus/notification-pagerduty"; 5 4 import { getLimit } from "@openstatus/db/src/schema/plan/utils"; 5 + import { PagerDutySchema } from "@openstatus/notification-pagerduty"; 6 6 7 7 import { z } from "zod"; 8 8
+1 -1
apps/web/src/app/app/[workspaceSlug]/(dashboard)/status-pages/[id]/reports/[reportId]/edit/page.tsx
··· 23 23 { 24 24 ...statusUpdate, 25 25 monitors: statusUpdate?.monitorsToStatusReports.map( 26 - ({ monitorId }) => monitorId 26 + ({ monitorId }) => monitorId, 27 27 ), 28 28 message: "", 29 29 }
+1 -1
apps/web/src/app/app/[workspaceSlug]/(dashboard)/status-pages/[id]/reports/[reportId]/overview/_components/header.tsx
··· 31 31 const [open, setOpen] = useState(false); 32 32 33 33 const firstUpdate = report.statusReportUpdates?.[0]; 34 - const lastUpdate = 34 + const _lastUpdate = 35 35 report.statusReportUpdates?.[report.statusReportUpdates?.length - 1]; 36 36 37 37 return (
+1 -1
apps/web/src/app/app/[workspaceSlug]/onboarding/page.tsx
··· 4 4 import { Button } from "@openstatus/ui"; 5 5 6 6 import { Header } from "@/components/dashboard/header"; 7 + import { MonitorForm } from "@/components/forms/monitor/form"; 7 8 import { StatusPageForm } from "@/components/forms/status-page/form"; 8 9 import { api } from "@/trpc/server"; 9 10 import { Description } from "./_components/description"; 10 - import { MonitorForm } from "@/components/forms/monitor/form"; 11 11 12 12 export default async function Onboarding({ 13 13 params,
+4 -4
apps/web/src/app/play/checker/_components/global-monitoring.tsx
··· 2 2 3 3 import { Button } from "@openstatus/ui"; 4 4 5 + import { Shell } from "@/components/dashboard/shell"; 6 + import type { ValidIcon } from "@/components/icons"; 5 7 import { 6 8 CardContainer, 9 + CardContent, 7 10 CardFeature, 8 11 CardFeatureContainer, 9 - CardIcon, 10 - CardContent, 11 12 CardHeader, 13 + CardIcon, 12 14 CardTitle, 13 15 } from "@/components/marketing/card"; 14 16 import { Globe } from "@/components/marketing/monitor/globe"; 15 - import type { ValidIcon } from "@/components/icons"; 16 - import { Shell } from "@/components/dashboard/shell"; 17 17 18 18 const features: { 19 19 icon: ValidIcon;
+2 -2
apps/web/src/app/play/checker/page.tsx
··· 1 1 import type { Metadata } from "next"; 2 2 3 3 import { BackButton } from "@/components/layout/back-button"; 4 - import CheckerPlay from "./_components/checker-play"; 5 - import { Testimonial } from "./_components/testimonial"; 6 4 import { BottomCTA } from "@/components/marketing/in-between-cta"; 5 + import CheckerPlay from "./_components/checker-play"; 7 6 import { GlobalMonitoring } from "./_components/global-monitoring"; 7 + import { Testimonial } from "./_components/testimonial"; 8 8 9 9 export const metadata: Metadata = { 10 10 title: "Speed Checker",
+2 -2
apps/web/src/app/play/status/_components/timezone-combobox.tsx
··· 77 77 currentValue === value 78 78 ? null // remove search param and use default timezone 79 79 : timezones.find( 80 - (timezone) => timezone.value === currentValue 80 + (timezone) => timezone.value === currentValue, 81 81 )?.label || null, 82 82 }); 83 83 ··· 89 89 <Check 90 90 className={cn( 91 91 "mr-2 h-4 w-4", 92 - value === timezone.value ? "opacity-100" : "opacity-0" 92 + value === timezone.value ? "opacity-100" : "opacity-0", 93 93 )} 94 94 /> 95 95 {timezone.label}
+1 -1
apps/web/src/app/pricing/page.tsx
··· 6 6 import { EnterpricePlan } from "@/components/marketing/pricing/enterprice-plan"; 7 7 import { PricingSlider } from "@/components/marketing/pricing/pricing-slider"; 8 8 import { PricingWrapperSuspense } from "@/components/marketing/pricing/pricing-wrapper"; 9 + import { Separator } from "@openstatus/ui"; 9 10 import { 10 11 defaultMetadata, 11 12 ogMetadata, 12 13 twitterMetadata, 13 14 } from "../shared-metadata"; 14 - import { Separator } from "@openstatus/ui"; 15 15 16 16 export const metadata: Metadata = { 17 17 ...defaultMetadata,
+1 -1
apps/web/src/app/public/monitors/[id]/page.tsx
··· 46 46 value 47 47 ?.trim() 48 48 ?.split(",") 49 - .filter((i) => flyRegions.includes(i as Region)) ?? [] 49 + .filter((i) => flyRegions.includes(i as Region)) ?? [], 50 50 ), 51 51 }); 52 52
+1 -1
apps/web/src/app/status-page/[domain]/_components/password-form.tsx
··· 116 116 <button 117 117 onClick={() => 118 118 setInputType((type) => 119 - type === "password" ? "text" : "password" 119 + type === "password" ? "text" : "password", 120 120 ) 121 121 } 122 122 >
+1 -1
apps/web/src/app/status-page/[domain]/monitors/[id]/page.tsx
··· 46 46 value 47 47 ?.trim() 48 48 ?.split(",") 49 - .filter((i) => flyRegions.includes(i as Region)) ?? [] 49 + .filter((i) => flyRegions.includes(i as Region)) ?? [], 50 50 ), 51 51 }); 52 52
+2 -2
apps/web/src/app/status-page/[domain]/monitors/page.tsx
··· 53 53 }); 54 54 55 55 return { monitor, data }; 56 - }) 56 + }), 57 57 ) 58 58 : undefined; 59 59 ··· 83 83 groupDataByTimestamp( 84 84 data.map((data) => ({ ...data, region: "ams" })), 85 85 period, 86 - quantile 86 + quantile, 87 87 ); 88 88 return ( 89 89 <li key={monitor.id} className="grid gap-2">
+1 -1
apps/web/src/app/status-page/[domain]/page.tsx
··· 24 24 const currentMaintenances = page.maintenances.filter( 25 25 (maintenance) => 26 26 maintenance.to.getTime() > Date.now() && 27 - maintenance.from.getTime() < Date.now() 27 + maintenance.from.getTime() < Date.now(), 28 28 ); 29 29 30 30 return (
+1 -1
apps/web/src/components/billing/pro-feature-alert.tsx
··· 4 4 import Link from "next/link"; 5 5 import { useParams } from "next/navigation"; 6 6 7 - import { Alert, AlertDescription, AlertTitle } from "@openstatus/ui"; 8 7 import type { WorkspacePlan } from "@openstatus/db/src/schema/workspaces/validation"; 8 + import { Alert, AlertDescription, AlertTitle } from "@openstatus/ui"; 9 9 10 10 interface Props { 11 11 feature: string;
+1 -1
apps/web/src/components/content/mdx.tsx
··· 16 16 <div 17 17 className={cn( 18 18 "prose prose-slate dark:prose-invert prose-pre:my-0 prose-img:rounded-lg prose-pre:rounded-lg prose-img:border prose-pre:border prose-img:border-border prose-pre:border-border prose-headings:font-cal prose-headings:font-normal", 19 - className 19 + className, 20 20 )} 21 21 > 22 22 <MDXComponent components={{ ...components }} />
+2 -2
apps/web/src/components/data-table/data-table.tsx
··· 94 94 ? null 95 95 : flexRender( 96 96 header.column.columnDef.header, 97 - header.getContext() 97 + header.getContext(), 98 98 )} 99 99 </TableHead> 100 100 ); ··· 121 121 <TableCell key={cell.id}> 122 122 {flexRender( 123 123 cell.column.columnDef.cell, 124 - cell.getContext() 124 + cell.getContext(), 125 125 )} 126 126 </TableCell> 127 127 ))}
+3 -3
apps/web/src/components/data-table/monitor/columns.tsx
··· 30 30 import { isActiveMaintenance } from "@/lib/maintenances/utils"; 31 31 32 32 import { Eye, EyeOff, Radio, View } from "lucide-react"; 33 - import { DataTableRowActions } from "./data-table-row-actions"; 34 - import { DataTableColumnHeader } from "./data-table-column-header"; 35 33 import type { ReactNode } from "react"; 34 + import { DataTableColumnHeader } from "./data-table-column-header"; 35 + import { DataTableRowActions } from "./data-table-row-actions"; 36 36 37 37 export const columns: ColumnDef<{ 38 38 monitor: Monitor; ··· 129 129 // REMINDER: if one value is found, return true 130 130 // we could consider restricting it to all the values have to be found 131 131 return value.some((item) => 132 - row.original.tags?.some((tag) => tag.name === item) 132 + row.original.tags?.some((tag) => tag.name === item), 133 133 ); 134 134 }, 135 135 },
+1 -1
apps/web/src/components/data-table/monitor/data-table-row-actions.tsx
··· 28 28 29 29 import { LoadingAnimation } from "@/components/loading-animation"; 30 30 import type { RegionChecker } from "@/components/ping-response-analysis/utils"; 31 - import { toastAction, toast } from "@/lib/toast"; 31 + import { toast, toastAction } from "@/lib/toast"; 32 32 import { api } from "@/trpc/client"; 33 33 interface DataTableRowActionsProps<TData> { 34 34 row: Row<TData>;
+2 -2
apps/web/src/components/data-table/monitor/data-table.tsx
··· 122 122 ? null 123 123 : flexRender( 124 124 header.column.columnDef.header, 125 - header.getContext() 125 + header.getContext(), 126 126 )} 127 127 </TableHead> 128 128 ); ··· 141 141 <TableCell key={cell.id}> 142 142 {flexRender( 143 143 cell.column.columnDef.cell, 144 - cell.getContext() 144 + cell.getContext(), 145 145 )} 146 146 </TableCell> 147 147 ))}
+2 -2
apps/web/src/components/data-table/notification/columns.tsx
··· 5 5 import type { Monitor, Notification } from "@openstatus/db/src/schema"; 6 6 import { Badge } from "@openstatus/ui"; 7 7 8 - import { DataTableRowActions } from "./data-table-row-actions"; 8 + import Link from "next/link"; 9 9 import { z } from "zod"; 10 10 import { DataTableBadges } from "../data-table-badges"; 11 - import Link from "next/link"; 11 + import { DataTableRowActions } from "./data-table-row-actions"; 12 12 13 13 // TODO: use the getProviderMetaData function from the notification form to access the data 14 14
+1 -1
apps/web/src/components/data-table/status-page/columns.tsx
··· 14 14 } from "@openstatus/ui"; 15 15 16 16 import { ArrowUpRight, Check } from "lucide-react"; 17 - import { DataTableRowActions } from "./data-table-row-actions"; 18 17 import { DataTableBadges } from "../data-table-badges"; 18 + import { DataTableRowActions } from "./data-table-row-actions"; 19 19 20 20 export const columns: ColumnDef< 21 21 Page & {
+6 -6
apps/web/src/components/forms/monitor/form.tsx
··· 1 1 "use client"; 2 2 3 3 import { zodResolver } from "@hookform/resolvers/zod"; 4 + import { getLimit } from "@openstatus/db/src/schema/plan/utils"; 4 5 import { usePathname, useRouter } from "next/navigation"; 5 6 import * as React from "react"; 6 7 import { useForm } from "react-hook-form"; 7 - import { getLimit } from "@openstatus/db/src/schema/plan/utils"; 8 8 9 9 import * as assertions from "@openstatus/assertions"; 10 10 import type { ··· 28 28 import { toast, toastAction } from "@/lib/toast"; 29 29 import { formatDuration } from "@/lib/utils"; 30 30 import { api } from "@/trpc/client"; 31 + import type { MonitorFlyRegion } from "@openstatus/db/src/schema/constants"; 32 + import type { Limits } from "@openstatus/db/src/schema/plan/schema"; 31 33 import { SaveButton } from "../shared/save-button"; 32 34 import { General } from "./general"; 33 35 import { RequestTestButton } from "./request-test-button"; ··· 37 39 import { SectionRequests } from "./section-requests"; 38 40 import { SectionScheduling } from "./section-scheduling"; 39 41 import { SectionStatusPage } from "./section-status-page"; 40 - import type { MonitorFlyRegion } from "@openstatus/db/src/schema/constants"; 41 - import type { Limits } from "@openstatus/db/src/schema/plan/schema"; 42 42 43 43 interface Props { 44 44 defaultSection?: string; ··· 145 145 finally: () => { 146 146 setPending(false); 147 147 }, 148 - } 148 + }, 149 149 ); 150 150 }; 151 151 ··· 194 194 JSON.stringify([ 195 195 ...(statusAssertions || []), 196 196 ...(headerAssertions || []), 197 - ]) 197 + ]), 198 198 ); 199 199 200 200 const data = (await res.json()) as RegionChecker; ··· 230 230 if (error instanceof Error && error.name === "AbortError") { 231 231 return { 232 232 error: `Abort error: request takes more then ${formatDuration( 233 - ABORT_TIMEOUT 233 + ABORT_TIMEOUT, 234 234 )}.`, 235 235 }; 236 236 }
+4 -4
apps/web/src/components/forms/monitor/request-test-button.tsx
··· 7 7 import { deserialize } from "@openstatus/assertions"; 8 8 import type { InsertMonitor } from "@openstatus/db/src/schema"; 9 9 import { 10 - flyRegions, 11 10 type MonitorFlyRegion, 11 + flyRegions, 12 12 } from "@openstatus/db/src/schema/constants"; 13 13 import { 14 14 Button, ··· 33 33 import { ResponseDetailTabs } from "@/components/ping-response-analysis/response-detail-tabs"; 34 34 import type { RegionChecker } from "@/components/ping-response-analysis/utils"; 35 35 import { toast, toastAction } from "@/lib/toast"; 36 - import { getLimit } from "@openstatus/db/src/schema/plan/utils"; 37 36 import type { Limits } from "@openstatus/db/src/schema/plan/schema"; 37 + import { getLimit } from "@openstatus/db/src/schema/plan/utils"; 38 38 39 39 interface Props { 40 40 form: UseFormReturn<InsertMonitor>; 41 41 limits: Limits; 42 42 pingEndpoint( 43 - region?: MonitorFlyRegion 43 + region?: MonitorFlyRegion, 44 44 ): Promise<{ data?: RegionChecker; error?: string }>; 45 45 } 46 46 ··· 146 146 JSON.stringify([ 147 147 ...(statusAssertions || []), 148 148 ...(headerAssertions || []), 149 - ]) 149 + ]), 150 150 )} 151 151 /> 152 152 </div>
+3 -3
apps/web/src/components/forms/monitor/section-assertions.tsx
··· 159 159 <SelectItem key={key} value={key}> 160 160 {value} 161 161 </SelectItem> 162 - ) 162 + ), 163 163 )} 164 164 </SelectContent> 165 165 </Select> ··· 220 220 <SelectItem key={key} value={key}> 221 221 {value} 222 222 </SelectItem> 223 - ) 223 + ), 224 224 )} 225 225 </SelectContent> 226 226 </Select> ··· 267 267 <SelectItem key={key} value={key}> 268 268 {value} 269 269 </SelectItem> 270 - ) 270 + ), 271 271 )} 272 272 </SelectContent> 273 273 </Select>
+2 -2
apps/web/src/components/forms/monitor/section-notifications.tsx
··· 70 70 ]) 71 71 : field.onChange( 72 72 field.value?.filter( 73 - (value) => value !== item.id 74 - ) 73 + (value) => value !== item.id, 74 + ), 75 75 ); 76 76 }} 77 77 >
+5 -5
apps/web/src/components/forms/monitor/section-scheduling.tsx
··· 21 21 } from "@openstatus/ui"; 22 22 import { groupByContinent } from "@openstatus/utils"; 23 23 24 + import type { Limits } from "@openstatus/db/src/schema/plan/schema"; 24 25 import { CheckboxLabel } from "../shared/checkbox-label"; 25 26 import { SectionHeader } from "../shared/section-header"; 26 - import type { Limits } from "@openstatus/db/src/schema/plan/schema"; 27 27 28 28 // TODO: centralize in a shared file! 29 29 const cronJobs = [ ··· 119 119 <div className="grid grid-cols-3 grid-rows-1 gap-2 pt-1"> 120 120 {current.regions 121 121 .sort((a, b) => 122 - a.location.localeCompare(b.location) 122 + a.location.localeCompare(b.location), 123 123 ) 124 124 .map((item) => { 125 125 return ( ··· 142 142 id={item.code} 143 143 name="region" 144 144 checked={field.value?.includes( 145 - item.code 145 + item.code, 146 146 )} 147 147 onCheckedChange={(checked) => { 148 148 console.log(field.value); ··· 156 156 : field.onChange( 157 157 field.value?.filter( 158 158 (value) => 159 - value !== item.code 160 - ) 159 + value !== item.code, 160 + ), 161 161 ); 162 162 }} 163 163 >
+2 -2
apps/web/src/components/forms/monitor/section-status-page.tsx
··· 88 88 ]) 89 89 : field.onChange( 90 90 field.value?.filter( 91 - (value) => value !== item.id 92 - ) 91 + (value) => value !== item.id, 92 + ), 93 93 ); 94 94 }} 95 95 >
+2 -2
apps/web/src/components/forms/monitor/tags-multi-box.tsx
··· 96 96 onChange( 97 97 values?.includes(item.id) 98 98 ? values.filter((v) => v !== item.id) 99 - : [...values, item.id] 99 + : [...values, item.id], 100 100 ); 101 101 inputRef?.current?.focus(); 102 102 }; ··· 170 170 <Check 171 171 className={cn( 172 172 "mr-2 h-4 w-4", 173 - isActive ? "opacity-100" : "opacity-0" 173 + isActive ? "opacity-100" : "opacity-0", 174 174 )} 175 175 /> 176 176 <div className="flex-1">{item.name}</div>
+2 -2
apps/web/src/components/forms/notification/config.ts
··· 2 2 InsertNotification, 3 3 NotificationProvider, 4 4 } from "@openstatus/db/src/schema"; 5 + import { allPlans } from "@openstatus/db/src/schema/plan/config"; 6 + import { workspacePlans } from "@openstatus/db/src/schema/workspaces/constants"; 5 7 import { sendTestDiscordMessage } from "@openstatus/notification-discord"; 6 8 import { sendTestSlackMessage } from "@openstatus/notification-slack"; 7 - import { allPlans } from "@openstatus/db/src/schema/plan/config"; 8 - import { workspacePlans } from "@openstatus/db/src/schema/workspaces/constants"; 9 9 export function getDefaultProviderData(defaultValues?: InsertNotification) { 10 10 if (!defaultValues?.provider) return ""; // FIXME: input can empty - needs to be undefined 11 11 return JSON.parse(defaultValues?.data || "{}")[defaultValues?.provider];
+3 -3
apps/web/src/components/forms/notification/general.tsx
··· 18 18 Input, 19 19 } from "@openstatus/ui"; 20 20 21 + import { LoadingAnimation } from "@/components/loading-animation"; 22 + import { toastAction } from "@/lib/toast"; 21 23 import { SectionHeader } from "../shared/section-header"; 22 24 import { getProviderMetaData } from "./config"; 23 - import { toastAction } from "@/lib/toast"; 24 - import { LoadingAnimation } from "@/components/loading-animation"; 25 25 26 26 interface Props { 27 27 form: UseFormReturn<InsertNotification>; ··· 34 34 const watchWebhookUrl = form.watch("data"); 35 35 const providerMetaData = useMemo( 36 36 () => getProviderMetaData(watchProvider), 37 - [watchProvider] 37 + [watchProvider], 38 38 ); 39 39 40 40 async function sendTestWebhookPing() {
+2 -2
apps/web/src/components/forms/notification/section-connect.tsx
··· 57 57 ]) 58 58 : field.onChange( 59 59 field.value?.filter( 60 - (value) => value !== item.id 61 - ) 60 + (value) => value !== item.id, 61 + ), 62 62 ); 63 63 }} 64 64 className="flex-col items-start truncate"
+1 -1
apps/web/src/components/forms/shared/checkbox-label.tsx
··· 37 37 htmlFor={`${name}-${id}`} 38 38 className={cn( 39 39 "flex h-full items-center gap-1 rounded-md border border-border bg-popover p-4 pr-10 [&:has([data-state=checked])]:border-primary peer-data-[state=checked]:border-primary hover:bg-accent hover:text-accent-foreground", 40 - className 40 + className, 41 41 )} 42 42 > 43 43 {children}
+3 -3
apps/web/src/components/forms/status-page/section-monitor.tsx
··· 77 77 const splitValue = currentValue.split("-"); 78 78 const id = splitValue?.[splitValue.length - 1]; 79 79 const monitorIndex = watchMonitors.findIndex( 80 - (m) => m.monitorId === Number.parseInt(id) 80 + (m) => m.monitorId === Number.parseInt(id), 81 81 ); 82 82 if (monitorIndex !== -1) { 83 83 remove(monitorIndex); ··· 100 100 "ml-auto h-4 w-4 shrink-0", 101 101 watchMonitors.some((m) => m.monitorId === monitor.id) 102 102 ? "opacity-100" 103 - : "opacity-0" 103 + : "opacity-0", 104 104 )} 105 105 /> 106 106 </CommandItem> ··· 126 126 <div className="w-full space-y-2"> 127 127 {fields.map((field) => { 128 128 const monitor = monitors?.find( 129 - ({ id }) => field.monitorId === id 129 + ({ id }) => field.monitorId === id, 130 130 ); 131 131 if (!monitor) return null; 132 132 return (
+4 -4
apps/web/src/components/forms/status-report-form.tsx
··· 97 97 message, 98 98 pageId, 99 99 ...rest, 100 - } 100 + }, 101 101 ); 102 102 // include update on creation 103 103 if (statusReport?.id) { ··· 227 227 ]) 228 228 : field.onChange( 229 229 field.value?.filter( 230 - (value) => value !== item.id 231 - ) 230 + (value) => value !== item.id, 231 + ), 232 232 ); 233 233 }} 234 234 /> ··· 243 243 "rounded-full p-1", 244 244 item.active 245 245 ? "bg-green-500" 246 - : "bg-red-500" 246 + : "bg-red-500", 247 247 )} 248 248 /> 249 249 </div>
+1 -1
apps/web/src/components/forms/status-report/form.tsx
··· 78 78 message, 79 79 pageId, 80 80 ...rest, 81 - } 81 + }, 82 82 ); 83 83 // include update on creation 84 84 if (statusReport?.id) {
+2 -2
apps/web/src/components/forms/status-report/section-connect.tsx
··· 63 63 ]) 64 64 : field.onChange( 65 65 field.value?.filter( 66 - (value) => value !== item.id 67 - ) 66 + (value) => value !== item.id, 67 + ), 68 68 ); 69 69 }} 70 70 >
+5 -5
apps/web/src/components/layout/marketing-header.tsx
··· 32 32 <header 33 33 className={cn( 34 34 "sticky top-3 z-10 flex w-full items-center justify-between gap-8 rounded-full border border-border px-2.5 py-1.5 backdrop-blur-lg md:top-6", 35 - className 35 + className, 36 36 )} 37 37 > 38 38 <div className="flex items-center gap-6"> ··· 41 41 </div> 42 42 <div 43 43 className={cn( 44 - "mx-auto hidden items-center justify-center border border-transparent md:flex md:gap-1" 44 + "mx-auto hidden items-center justify-center border border-transparent md:flex md:gap-1", 45 45 )} 46 46 > 47 47 <NavigationMenu> 48 48 <NavigationMenuList> 49 49 {marketingPagesConfig.map((page) => { 50 - const { href, title, segment, children } = page; 50 + const { href, title, children } = page; 51 51 if (!children) { 52 52 return ( 53 53 <NavigationMenuItem key={title}> ··· 56 56 className={cn( 57 57 navigationMenuTriggerStyle(), 58 58 "h-9 rounded-full bg-transparent text-muted-foreground hover:bg-accent/50", 59 - { "text-foreground": href === pathname } 59 + { "text-foreground": href === pathname }, 60 60 )} 61 61 > 62 62 {title} ··· 122 122 ref={ref} 123 123 className={cn( 124 124 "flex select-none gap-3 space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors focus:bg-accent hover:bg-accent focus:text-accent-foreground hover:text-accent-foreground", 125 - className 125 + className, 126 126 )} 127 127 {...props} 128 128 >
+2 -2
apps/web/src/components/layout/marketing-menu.tsx
··· 132 132 ref={ref} 133 133 className={cn( 134 134 "flex select-none items-center gap-2 space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors focus:bg-accent hover:bg-accent focus:text-accent-foreground hover:text-accent-foreground", 135 - className 135 + className, 136 136 )} 137 137 {...props} 138 138 > ··· 156 156 ref={ref} 157 157 className={cn( 158 158 "flex flex-1 items-center justify-between border-b py-4 font-medium transition-all hover:underline", 159 - className 159 + className, 160 160 )} 161 161 {...props} 162 162 >
+1 -1
apps/web/src/components/marketing/monitor/globe.tsx
··· 19 19 export function Globe() { 20 20 const canvasRef = useRef<HTMLCanvasElement>(null); 21 21 const prefersReducedMotion = useMediaQuery( 22 - "(prefers-reduced-motion: reduce)" 22 + "(prefers-reduced-motion: reduce)", 23 23 ); 24 24 const [disabledWebGL, setDisabledWebGL] = useState(false); 25 25
+2 -2
apps/web/src/components/marketing/pricing/pricing-plan-radio.tsx
··· 1 1 "use client"; 2 2 3 - import { useRouter } from "next/navigation"; 4 3 import { allPlans } from "@openstatus/db/src/schema/plan/config"; 5 4 import { workspacePlans } from "@openstatus/db/src/schema/workspaces/constants"; 6 5 import { Label, RadioGroup, RadioGroupItem } from "@openstatus/ui"; 6 + import { useRouter } from "next/navigation"; 7 7 8 8 import useUpdateSearchParams from "@/hooks/use-update-search-params"; 9 9 import { cn } from "@/lib/utils"; ··· 27 27 htmlFor={key} 28 28 className={cn( 29 29 "flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 [&:has([data-state=checked])]:border-primary peer-data-[state=checked]:border-primary hover:bg-accent hover:text-accent-foreground", 30 - key === "team" && "bg-muted/50" 30 + key === "team" && "bg-muted/50", 31 31 )} 32 32 > 33 33 <span className="text-sm capitalize">{key}</span>
+2 -2
apps/web/src/components/marketing/pricing/pricing-slider.tsx
··· 77 77 onValueChange={setIndex} 78 78 /> 79 79 <div className="flex items-center justify-between"> 80 - {slides.map((slide, i) => ( 80 + {slides.map((slide, _i) => ( 81 81 // TODO: make them clickable 82 82 <div 83 83 key={slide.key} 84 84 className={cn( 85 - "text-left font-mono text-muted-foreground text-xs" 85 + "text-left font-mono text-muted-foreground text-xs", 86 86 )} 87 87 > 88 88 {slide.key}
+5 -5
apps/web/src/components/marketing/pricing/pricing-table.tsx
··· 4 4 import { useRouter } from "next/navigation"; 5 5 import { Fragment } from "react"; 6 6 7 + import { workspacePlans } from "@openstatus/db/src/schema/workspaces/constants"; 7 8 import type { WorkspacePlan } from "@openstatus/db/src/schema/workspaces/validation"; 8 - import { pricingTableConfig } from "../../../config/pricing-table"; 9 - import { workspacePlans } from "@openstatus/db/src/schema/workspaces/constants"; 10 9 import { 11 10 Badge, 12 11 Button, ··· 18 17 TableHeader, 19 18 TableRow, 20 19 } from "@openstatus/ui"; 20 + import { pricingTableConfig } from "../../../config/pricing-table"; 21 21 22 22 import { LoadingAnimation } from "@/components/loading-animation"; 23 23 import { cn } from "@/lib/utils"; ··· 55 55 key={key} 56 56 className={cn( 57 57 "h-auto px-3 py-3 align-bottom text-foreground", 58 - key === "team" ? "bg-muted/30" : "bg-background" 58 + key === "team" ? "bg-muted/30" : "bg-background", 59 59 )} 60 60 > 61 61 <p className="sticky top-0 mb-2 font-cal text-2xl"> ··· 160 160 key={key + value + _i} 161 161 className={cn( 162 162 "p-3", 163 - plan.key === "team" && "bg-muted/30" 163 + plan.key === "team" && "bg-muted/30", 164 164 )} 165 165 > 166 166 {renderContent()} ··· 172 172 })} 173 173 </Fragment> 174 174 ); 175 - } 175 + }, 176 176 )} 177 177 </TableBody> 178 178 </Table>
+1 -1
apps/web/src/components/marketing/status-page/tracker-example.tsx
··· 35 35 }, 36 36 { 37 37 revalidate: 600, // 10 minutes 38 - } 38 + }, 39 39 ); 40 40 41 41 if (!data) return null;
+1 -1
apps/web/src/components/monitor-charts/chart.tsx
··· 79 79 <div 80 80 className={cn( 81 81 "flex w-1 flex-col rounded", 82 - `bg-${category.color}-500` 82 + `bg-${category.color}-500`, 83 83 )} 84 84 /> 85 85 <div className="flex w-full justify-between gap-2">
+2 -2
apps/web/src/components/monitor-charts/simple-chart.tsx
··· 3 3 import type { CustomTooltipProps } from "@tremor/react"; 4 4 import { LineChart } from "@tremor/react"; 5 5 6 - import { dataFormatter } from "./utils"; 7 6 import { cn } from "@/lib/utils"; 7 + import { dataFormatter } from "./utils"; 8 8 9 9 export interface SimpleChartProps { 10 10 data: { timestamp: string; [key: string]: string }[]; ··· 51 51 <div 52 52 className={cn( 53 53 "flex w-1 flex-col rounded", 54 - `bg-${category.color}-500` 54 + `bg-${category.color}-500`, 55 55 )} 56 56 /> 57 57 <div className="flex flex-col gap-1">
+25 -22
apps/web/src/components/monitor-charts/utils.tsx
··· 14 14 export function groupDataByTimestamp( 15 15 data: ResponseGraph[], 16 16 period: Period, 17 - quantile: Quantile 17 + quantile: Quantile, 18 18 ) { 19 19 let currentTimestamp = 0; 20 20 const regions: Record< 21 21 string, 22 22 { code: string; location: string; flag: string } 23 23 > = {}; 24 - const _data = data.reduce((acc, curr) => { 25 - const { timestamp, region } = curr; 26 - const latency = curr[`${quantile}Latency`]; 27 - const { flag, code, location } = flyRegionsDict[region]; 28 - const fullNameRegion = `${code}`; 29 - regions[fullNameRegion] = { flag, code, location }; // to get the region keys 30 - if (timestamp === currentTimestamp) { 31 - // overwrite last object in acc 32 - const last = acc.pop(); 33 - if (last) { 24 + const _data = data.reduce( 25 + (acc, curr) => { 26 + const { timestamp, region } = curr; 27 + const latency = curr[`${quantile}Latency`]; 28 + const { flag, code, location } = flyRegionsDict[region]; 29 + const fullNameRegion = `${code}`; 30 + regions[fullNameRegion] = { flag, code, location }; // to get the region keys 31 + if (timestamp === currentTimestamp) { 32 + // overwrite last object in acc 33 + const last = acc.pop(); 34 + if (last) { 35 + acc.push({ 36 + ...last, 37 + [fullNameRegion]: latency, 38 + }); 39 + } 40 + } else if (timestamp) { 41 + currentTimestamp = timestamp; 42 + // create new object in acc 34 43 acc.push({ 35 - ...last, 44 + timestamp: renderTimestamp(timestamp, period), 36 45 [fullNameRegion]: latency, 37 46 }); 38 47 } 39 - } else if (timestamp) { 40 - currentTimestamp = timestamp; 41 - // create new object in acc 42 - acc.push({ 43 - timestamp: renderTimestamp(timestamp, period), 44 - [fullNameRegion]: latency, 45 - }); 46 - } 47 - return acc; 48 - }, [] as (Partial<Record<Region, string>> & { timestamp: string })[]); 48 + return acc; 49 + }, 50 + [] as (Partial<Record<Region, string>> & { timestamp: string })[], 51 + ); 49 52 50 53 // regions are sorted by the flag utf-8 code 51 54 return {
+14 -11
apps/web/src/components/monitor-dashboard/region-preset.tsx
··· 53 53 } 54 54 }, [allSelected, router, pathname, updateSearchParams, selected]); 55 55 56 - const regionsByContinent = regions.reduce((prev, curr) => { 57 - const region = flyRegionsDict[curr]; 56 + const regionsByContinent = regions.reduce( 57 + (prev, curr) => { 58 + const region = flyRegionsDict[curr]; 58 59 59 - if (prev[region.continent]) { 60 - prev[region.continent].push(region); 61 - } else { 62 - prev[region.continent] = [region]; 63 - } 60 + if (prev[region.continent]) { 61 + prev[region.continent].push(region); 62 + } else { 63 + prev[region.continent] = [region]; 64 + } 64 65 65 - return prev; 66 - }, {} as Record<Continent, RegionInfo[]>); 66 + return prev; 67 + }, 68 + {} as Record<Continent, RegionInfo[]>, 69 + ); 67 70 68 71 return ( 69 72 <Popover> ··· 115 118 setSelected((prev) => 116 119 !prev.includes(checked as Region) 117 120 ? [...prev, code] 118 - : prev.filter((r) => r !== code) 121 + : prev.filter((r) => r !== code), 119 122 ); 120 123 }} 121 124 > ··· 124 127 "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary", 125 128 isSelected 126 129 ? "bg-primary text-primary-foreground" 127 - : "opacity-50 [&_svg]:invisible" 130 + : "opacity-50 [&_svg]:invisible", 128 131 )} 129 132 > 130 133 <Check className={cn("h-4 w-4")} />
+1 -1
apps/web/src/components/ping-response-analysis/columns.tsx
··· 1 1 "use client"; 2 2 3 3 import type { ColumnDef } from "@tanstack/react-table"; 4 - import { latencyFormatter, regionFormatter, type RegionChecker } from "./utils"; 4 + import { type RegionChecker, latencyFormatter, regionFormatter } from "./utils"; 5 5 6 6 import { DataTableColumnHeader } from "../data-table/data-table-column-header"; 7 7 import { StatusCodeBadge } from "../monitor/status-code-badge";
+1 -1
apps/web/src/components/ping-response-analysis/multi-region-chart.tsx
··· 10 10 .sort((a, b) => a.latency - b.latency) 11 11 .map((item) => { 12 12 const { dns, connection, tls, ttfb, transfer } = getTimingPhases( 13 - item.timing 13 + item.timing, 14 14 ); 15 15 return { 16 16 region: regionFormatter(item.region),
+3 -3
apps/web/src/components/ping-response-analysis/multi-region-table.tsx
··· 12 12 13 13 import { 14 14 type ColumnDef, 15 - getCoreRowModel, 16 - useReactTable, 17 - flexRender, 18 15 type SortingState, 16 + flexRender, 17 + getCoreRowModel, 19 18 getSortedRowModel, 19 + useReactTable, 20 20 } from "@tanstack/react-table"; 21 21 import { useState } from "react"; 22 22
+1 -1
apps/web/src/components/ping-response-analysis/multi-region-tabs.tsx
··· 1 1 import { Tabs, TabsContent, TabsList, TabsTrigger } from "@openstatus/ui"; 2 2 3 + import { columns } from "./columns"; 3 4 import { MultiRegionChart } from "./multi-region-chart"; 4 5 import { MultiRegionTable } from "./multi-region-table"; 5 6 import type { RegionChecker } from "./utils"; 6 - import { columns } from "./columns"; 7 7 8 8 export function MultiRegionTabs({ regions }: { regions: RegionChecker[] }) { 9 9 return (
+4 -4
apps/web/src/components/ping-response-analysis/utils.ts
··· 18 18 19 19 export function regionFormatter( 20 20 region: MonitorFlyRegion, 21 - type: "short" | "long" = "short" 21 + type: "short" | "long" = "short", 22 22 ) { 23 23 const { code, flag, location } = flyRegionsDict[region]; 24 24 if (type === "short") return `${code} ${flag}`; ··· 121 121 method?: Method; 122 122 headers?: { value: string; key: string }[]; 123 123 body?: string; 124 - } 124 + }, 125 125 ): Promise<RegionChecker> { 126 126 // 127 127 const res = await fetch(`https://checker.openstatus.dev/ping/${region}`, { ··· 155 155 if (!data.success) { 156 156 console.log(json); 157 157 console.error( 158 - `something went wrong with result ${json} request to ${url} error ${data.error.message}` 158 + `something went wrong with result ${json} request to ${url} error ${data.error.message}`, 159 159 ); 160 160 throw new Error(data.error.message); 161 161 } ··· 172 172 flyRegions.map(async (region) => { 173 173 const check = await checkRegion(url, region, opts); 174 174 return check; 175 - }) 175 + }), 176 176 ); 177 177 } 178 178
+1 -1
apps/web/src/components/status-page/datetime-tooltip.tsx
··· 26 26 onClick={() => setOpen(false)} 27 27 className={cn( 28 28 "text-muted-foreground underline decoration-muted-foreground/30 decoration-dashed underline-offset-4", 29 - className 29 + className, 30 30 )} 31 31 asChild 32 32 >
+1 -1
apps/web/src/components/status-page/status-report.tsx
··· 14 14 import { setPrefixUrl } from "@/app/status-page/[domain]/utils"; 15 15 import { cn } from "@/lib/utils"; 16 16 import { StatusBadge } from "../status-update/status-badge"; 17 - import { ProcessMessage } from "./process-message"; 18 17 import { DateTimeTooltip } from "./datetime-tooltip"; 18 + import { ProcessMessage } from "./process-message"; 19 19 20 20 function StatusReport({ 21 21 report,
+2 -2
apps/web/src/components/tracker/tracker.tsx
··· 155 155 <div 156 156 className={cn( 157 157 rootClassName, 158 - "h-auto w-1 flex-none rounded-full" 158 + "h-auto w-1 flex-none rounded-full", 159 159 )} 160 160 /> 161 161 <div className="grid flex-1 gap-1"> ··· 250 250 Down for{" "} 251 251 {formatDuration( 252 252 { minutes, hours, days }, 253 - { format: ["days", "hours", "minutes", "seconds"], zero: false } 253 + { format: ["days", "hours", "minutes", "seconds"], zero: false }, 254 254 )} 255 255 </p> 256 256 );
+7 -7
packages/api/src/router/invitation.ts
··· 34 34 where: and( 35 35 eq(invitation.workspaceId, opts.ctx.workspace.id), 36 36 gte(invitation.expiresAt, new Date()), 37 - isNull(invitation.acceptedAt) 37 + isNull(invitation.acceptedAt), 38 38 ), 39 39 }) 40 40 ).length; ··· 60 60 61 61 if (process.env.NODE_ENV === "development") { 62 62 console.log( 63 - `>>>> Invitation token: http://localhost:3000/app/invite?token=${token} <<<< ` 63 + `>>>> Invitation token: http://localhost:3000/app/invite?token=${token} <<<< `, 64 64 ); 65 65 } else { 66 66 await fetch("https://api.resend.com/emails", { ··· 104 104 .where( 105 105 and( 106 106 eq(invitation.id, opts.input.id), 107 - eq(invitation.workspaceId, opts.ctx.workspace.id) 108 - ) 107 + eq(invitation.workspaceId, opts.ctx.workspace.id), 108 + ), 109 109 ) 110 110 .run(); 111 111 }), ··· 115 115 where: and( 116 116 eq(invitation.workspaceId, opts.ctx.workspace.id), 117 117 gte(invitation.expiresAt, new Date()), 118 - isNull(invitation.acceptedAt) 118 + isNull(invitation.acceptedAt), 119 119 ), 120 120 }); 121 121 return _invitations; ··· 144 144 z.object({ 145 145 message: z.string(), 146 146 data: selectWorkspaceSchema.optional(), 147 - }) 147 + }), 148 148 ) 149 149 .mutation(async (opts) => { 150 150 const _invitation = await opts.ctx.db.query.invitation.findFirst({ 151 151 where: and( 152 152 eq(invitation.token, opts.input.token), 153 - isNull(invitation.acceptedAt) 153 + isNull(invitation.acceptedAt), 154 154 ), 155 155 with: { 156 156 workspace: true,
+15 -12
packages/api/src/router/notification.ts
··· 14 14 15 15 import { SchemaError } from "@openstatus/error"; 16 16 import { trackNewNotification } from "../analytics"; 17 - import { createTRPCRouter, protectedProcedure } from "../trpc"; 18 17 import { env } from "../env"; 18 + import { createTRPCRouter, protectedProcedure } from "../trpc"; 19 19 20 20 export const notificationRouter = createTRPCRouter({ 21 21 create: protectedProcedure ··· 94 94 .where( 95 95 and( 96 96 eq(notification.id, opts.input.id), 97 - eq(notification.workspaceId, opts.ctx.workspace.id) 98 - ) 97 + eq(notification.workspaceId, opts.ctx.workspace.id), 98 + ), 99 99 ) 100 100 .returning() 101 101 .get(); ··· 106 106 const allMonitors = await opts.ctx.db.query.monitor.findMany({ 107 107 where: and( 108 108 eq(monitor.workspaceId, opts.ctx.workspace.id), 109 - inArray(monitor.id, monitors) 109 + inArray(monitor.id, monitors), 110 110 ), 111 111 }); 112 112 ··· 122 122 .select() 123 123 .from(notificationsToMonitors) 124 124 .where( 125 - eq(notificationsToMonitors.notificationId, currentNotification.id) 125 + eq(notificationsToMonitors.notificationId, currentNotification.id), 126 126 ) 127 127 .all(); 128 128 ··· 136 136 .where( 137 137 and( 138 138 inArray(notificationsToMonitors.monitorId, removedMonitors), 139 - eq(notificationsToMonitors.notificationId, currentNotification.id) 140 - ) 139 + eq( 140 + notificationsToMonitors.notificationId, 141 + currentNotification.id, 142 + ), 143 + ), 141 144 ); 142 145 } 143 146 ··· 164 167 .where( 165 168 and( 166 169 eq(notification.id, opts.input.id), 167 - eq(notification.id, opts.input.id) 168 - ) 170 + eq(notification.id, opts.input.id), 171 + ), 169 172 ) 170 173 .run(); 171 174 }), ··· 177 180 where: and( 178 181 eq(notification.id, opts.input.id), 179 182 eq(notification.id, opts.input.id), 180 - eq(notification.workspaceId, opts.ctx.workspace.id) 183 + eq(notification.workspaceId, opts.ctx.workspace.id), 181 184 ), 182 185 // FIXME: plural 183 186 with: { monitor: { with: { monitor: true } } }, ··· 187 190 monitor: z.array( 188 191 z.object({ 189 192 monitor: selectMonitorSchema, 190 - }) 193 + }), 191 194 ), 192 195 }); 193 196 ··· 207 210 monitor: z.array( 208 211 z.object({ 209 212 monitor: selectMonitorSchema, 210 - }) 213 + }), 211 214 ), 212 215 }); 213 216
+19 -19
packages/api/src/router/page.ts
··· 75 75 where: and( 76 76 inArray(monitor.id, monitorIds), 77 77 eq(monitor.workspaceId, opts.ctx.workspace.id), 78 - isNull(monitor.deletedAt) 78 + isNull(monitor.deletedAt), 79 79 ), 80 80 }); 81 81 ··· 105 105 const firstPage = await opts.ctx.db.query.page.findFirst({ 106 106 where: and( 107 107 eq(page.id, opts.input.id), 108 - eq(page.workspaceId, opts.ctx.workspace.id) 108 + eq(page.workspaceId, opts.ctx.workspace.id), 109 109 ), 110 110 with: { 111 111 monitorsToPages: { ··· 142 142 .where( 143 143 and( 144 144 eq(page.id, pageInput.id), 145 - eq(page.workspaceId, opts.ctx.workspace.id) 146 - ) 145 + eq(page.workspaceId, opts.ctx.workspace.id), 146 + ), 147 147 ) 148 148 .returning() 149 149 .get(); ··· 154 154 where: and( 155 155 inArray(monitor.id, monitorIds), 156 156 eq(monitor.workspaceId, opts.ctx.workspace.id), 157 - isNull(monitor.deletedAt) 157 + isNull(monitor.deletedAt), 158 158 ), 159 159 }); 160 160 ··· 183 183 .where( 184 184 and( 185 185 inArray(monitorsToPages.monitorId, removedMonitors), 186 - eq(monitorsToPages.pageId, currentPage.id) 187 - ) 186 + eq(monitorsToPages.pageId, currentPage.id), 187 + ), 188 188 ); 189 189 } 190 190 ··· 212 212 .where( 213 213 and( 214 214 eq(page.id, opts.input.id), 215 - eq(page.workspaceId, opts.ctx.workspace.id) 216 - ) 215 + eq(page.workspaceId, opts.ctx.workspace.id), 216 + ), 217 217 ) 218 218 .run(); 219 219 }), ··· 225 225 maintenancesToPages: { 226 226 where: and( 227 227 lte(maintenance.from, new Date()), 228 - gte(maintenance.to, new Date()) 228 + gte(maintenance.to, new Date()), 229 229 ), 230 230 }, 231 231 }, ··· 261 261 .leftJoin(monitor, eq(monitorsToPages.monitorId, monitor.id)) 262 262 .where( 263 263 // make sur only active monitors are returned! 264 - and(eq(monitorsToPages.pageId, result.id), eq(monitor.active, true)) 264 + and(eq(monitorsToPages.pageId, result.id), eq(monitor.active, true)), 265 265 ) 266 266 .all(); 267 267 268 268 const monitorsId = monitorsToPagesResult.map( 269 - ({ monitors_to_pages }) => monitors_to_pages.monitorId 269 + ({ monitors_to_pages }) => monitors_to_pages.monitorId, 270 270 ); 271 271 272 272 const monitorsToStatusReportResult = ··· 279 279 : []; 280 280 281 281 const monitorStatusReportIds = monitorsToStatusReportResult.map( 282 - ({ statusReportId }) => statusReportId 282 + ({ statusReportId }) => statusReportId, 283 283 ); 284 284 285 285 const statusReportIds = Array.from(new Set([...monitorStatusReportIds])); ··· 307 307 and( 308 308 inArray(monitor.id, monitorsId), 309 309 eq(monitor.active, true), 310 - isNull(monitor.deletedAt) 311 - ) // REMINDER: this is hardcoded 310 + isNull(monitor.deletedAt), 311 + ), // REMINDER: this is hardcoded 312 312 ) 313 313 .all() 314 314 : []; ··· 321 321 .where( 322 322 inArray( 323 323 incidentTable.monitorId, 324 - monitors.map((m) => m.id) 325 - ) 324 + monitors.map((m) => m.id), 325 + ), 326 326 ) 327 327 .all() 328 328 : []; ··· 361 361 // had filter on some words we want to keep for us 362 362 if ( 363 363 ["api", "app", "www", "docs", "checker", "time", "help"].includes( 364 - opts.input.slug 364 + opts.input.slug, 365 365 ) 366 366 ) { 367 367 return false; ··· 374 374 375 375 addCustomDomain: protectedProcedure 376 376 .input( 377 - z.object({ customDomain: z.string().toLowerCase(), pageId: z.number() }) 377 + z.object({ customDomain: z.string().toLowerCase(), pageId: z.number() }), 378 378 ) 379 379 .mutation(async (opts) => { 380 380 // TODO Add some check ?
+3 -3
packages/api/src/router/stripe/webhook.ts
··· 6 6 import { eq } from "@openstatus/db"; 7 7 import { user, workspace } from "@openstatus/db/src/schema"; 8 8 9 + import { getLimits } from "@openstatus/db/src/schema/plan/utils"; 9 10 import { createTRPCRouter, publicProcedure } from "../../trpc"; 10 11 import { stripe } from "./shared"; 11 12 import { getPlanFromPriceId } from "./utils"; 12 - import { getLimits } from "@openstatus/db/src/schema/plan/utils"; 13 13 14 14 const webhookProcedure = publicProcedure.input( 15 15 z.object({ ··· 23 23 }), 24 24 type: z.string(), 25 25 }), 26 - }) 26 + }), 27 27 ); 28 28 29 29 export const webhookRouter = createTRPCRouter({ ··· 36 36 }); 37 37 } 38 38 const subscription = await stripe.subscriptions.retrieve( 39 - session.subscription 39 + session.subscription, 40 40 ); 41 41 const customerId = 42 42 typeof subscription.customer === "string"
+4 -4
packages/api/src/router/tinybird/index.ts
··· 2 2 3 3 import { OSTinybird } from "@openstatus/tinybird"; 4 4 5 + import { flyRegions } from "@openstatus/db/src/schema/constants"; 5 6 import { env } from "../../env"; 6 7 import { createTRPCRouter, protectedProcedure } from "../../trpc"; 7 - import { flyRegions } from "@openstatus/db/src/schema/constants"; 8 8 9 9 const tb = new OSTinybird({ token: env.TINY_BIRD_API_KEY }); 10 10 ··· 29 29 url: z.string().url().optional(), 30 30 region: z.enum(flyRegions).optional(), 31 31 cronTimestamp: z.number().int().optional(), 32 - }) 32 + }), 33 33 ) 34 34 .query(async (opts) => { 35 35 return await tb.endpointResponseDetails("7d")(opts.input); ··· 52 52 dsn: z.string(), 53 53 path: z.string(), 54 54 period: z.enum(["24h", "7d", "30d"]), 55 - }) 55 + }), 56 56 ) 57 57 .query(async (opts) => { 58 58 return await tb.applicationRUMMetricsForPath()(opts.input); ··· 63 63 dsn: z.string(), 64 64 path: z.string(), 65 65 period: z.enum(["24h", "7d", "30d"]), 66 - }) 66 + }), 67 67 ) 68 68 .query(async (opts) => { 69 69 return await tb.applicationSessionMetricsPerPath()(opts.input);
+1 -1
packages/db/package.json
··· 33 33 "utf-8-validate": "6.0.3" 34 34 }, 35 35 "author": "OpenStatus" 36 - } 36 + }
+1 -1
packages/db/src/migrate.mts
··· 6 6 7 7 async function main() { 8 8 const db = drizzle( 9 - createClient({ url: env.DATABASE_URL, authToken: env.DATABASE_AUTH_TOKEN }) 9 + createClient({ url: env.DATABASE_URL, authToken: env.DATABASE_AUTH_TOKEN }), 10 10 ); 11 11 console.log("Running migrations"); 12 12
+3 -3
packages/db/src/schema/monitor_status/validation.ts
··· 1 1 import { createInsertSchema, createSelectSchema } from "drizzle-zod"; 2 2 import type { z } from "zod"; 3 3 4 + import { monitorRegionSchema } from "../constants"; 4 5 import { monitorStatusSchema } from "../monitors"; 5 6 import { monitorStatusTable } from "./monitor_status"; 6 - import { monitorRegionSchema } from "../constants"; 7 7 8 8 export const selectMonitorStatusSchema = createSelectSchema( 9 9 monitorStatusTable, 10 10 { 11 11 status: monitorStatusSchema.default("active"), 12 12 region: monitorRegionSchema.default("ams"), 13 - } 13 + }, 14 14 ); 15 15 16 16 export const insertMonitorStatusSchema = createInsertSchema( ··· 18 18 { 19 19 status: monitorStatusSchema.default("active"), 20 20 region: monitorRegionSchema.default("ams"), 21 - } 21 + }, 22 22 ); 23 23 24 24 // export type InsertMonitorStatus = z.infer<typeof insertMonitorStatusSchema>;
+6 -6
packages/db/src/schema/monitors/monitor.ts
··· 6 6 text, 7 7 } from "drizzle-orm/sqlite-core"; 8 8 9 + import { monitorPeriodicity } from "../constants"; 9 10 import { maintenancesToMonitors } from "../maintenances"; 10 11 import { monitorTagsToMonitors } from "../monitor_tags"; 11 12 import { notificationsToMonitors } from "../notifications"; ··· 13 14 import { monitorsToStatusReport } from "../status_reports"; 14 15 import { workspace } from "../workspaces/workspace"; 15 16 import { monitorJobTypes, monitorMethods, monitorStatus } from "./constants"; 16 - import { monitorPeriodicity } from "../constants"; 17 17 18 18 export const monitor = sqliteTable("monitor", { 19 19 id: integer("id").primaryKey(), ··· 49 49 public: integer("public", { mode: "boolean" }).default(false), 50 50 51 51 createdAt: integer("created_at", { mode: "timestamp" }).default( 52 - sql`(strftime('%s', 'now'))` 52 + sql`(strftime('%s', 'now'))`, 53 53 ), 54 54 updatedAt: integer("updated_at", { mode: "timestamp" }).default( 55 - sql`(strftime('%s', 'now'))` 55 + sql`(strftime('%s', 'now'))`, 56 56 ), 57 57 58 58 deletedAt: integer("deleted_at", { mode: "timestamp" }), ··· 80 80 .notNull() 81 81 .references(() => page.id, { onDelete: "cascade" }), 82 82 createdAt: integer("created_at", { mode: "timestamp" }).default( 83 - sql`(strftime('%s', 'now'))` 83 + sql`(strftime('%s', 'now'))`, 84 84 ), 85 85 order: integer("order").default(0), 86 86 }, 87 87 (t) => ({ 88 88 pk: primaryKey(t.monitorId, t.pageId), 89 - }) 89 + }), 90 90 ); 91 91 92 92 export const monitorsToPagesRelation = relations( ··· 100 100 fields: [monitorsToPages.pageId], 101 101 references: [page.id], 102 102 }), 103 - }) 103 + }), 104 104 );
+14 -11
packages/db/src/schema/monitors/validation.ts
··· 3 3 4 4 import * as assertions from "@openstatus/assertions"; 5 5 6 + import { monitorPeriodicitySchema, monitorRegionSchema } from "../constants"; 6 7 import { monitorJobTypes, monitorMethods, monitorStatus } from "./constants"; 7 8 import { monitor, monitorsToPages } from "./monitor"; 8 - import { monitorPeriodicitySchema, monitorRegionSchema } from "../constants"; 9 9 10 10 export const monitorMethodsSchema = z.enum(monitorMethods); 11 11 export const monitorStatusSchema = z.enum(monitorStatus); ··· 26 26 return String(val); 27 27 }, z.string()); 28 28 29 - const headersToArraySchema = z.preprocess((val) => { 30 - // early return in case the header is already an array 31 - if (Array.isArray(val)) { 32 - return val; 33 - } 34 - if (String(val).length > 0) { 35 - return JSON.parse(String(val)); 36 - } 37 - return []; 38 - }, z.array(z.object({ key: z.string(), value: z.string() })).default([])); 29 + const headersToArraySchema = z.preprocess( 30 + (val) => { 31 + // early return in case the header is already an array 32 + if (Array.isArray(val)) { 33 + return val; 34 + } 35 + if (String(val).length > 0) { 36 + return JSON.parse(String(val)); 37 + } 38 + return []; 39 + }, 40 + z.array(z.object({ key: z.string(), value: z.string() })).default([]), 41 + ); 39 42 40 43 export const selectMonitorSchema = createSelectSchema(monitor, { 41 44 periodicity: monitorPeriodicitySchema.default("10m"),
+3 -3
packages/db/src/schema/pages/page.ts
··· 22 22 // Password protecting the status page - no specific restriction on password 23 23 password: text("password", { length: 256 }), 24 24 passwordProtected: integer("password_protected", { mode: "boolean" }).default( 25 - false 25 + false, 26 26 ), 27 27 28 28 createdAt: integer("created_at", { mode: "timestamp" }).default( 29 - sql`(strftime('%s', 'now'))` 29 + sql`(strftime('%s', 'now'))`, 30 30 ), 31 31 updatedAt: integer("updated_at", { mode: "timestamp" }).default( 32 - sql`(strftime('%s', 'now'))` 32 + sql`(strftime('%s', 'now'))`, 33 33 ), 34 34 }); 35 35
+2 -2
packages/db/src/schema/plan/utils.ts
··· 1 - import type { Limits } from "./schema"; 2 - import { allPlans } from "./config"; 3 1 import type { WorkspacePlan } from "../workspaces/validation"; 2 + import { allPlans } from "./config"; 3 + import type { Limits } from "./schema"; 4 4 5 5 export function getLimit<T extends keyof Limits>(limits: Limits, limit: T) { 6 6 return limits[limit] || allPlans.free.limits[limit];
+4 -4
packages/db/src/schema/shared.ts
··· 26 26 monitorId: z.number(), 27 27 statusReportId: z.number(), 28 28 monitor: selectPublicMonitorSchema, 29 - }) 29 + }), 30 30 ) 31 31 .default([]), 32 32 }); ··· 37 37 z.object({ 38 38 monitorId: z.number(), 39 39 maintenanceId: z.number(), 40 - }) 40 + }), 41 41 ) 42 42 .default([]), 43 43 }); ··· 56 56 pageId: z.number(), 57 57 order: z.number().default(0).optional(), 58 58 monitor: selectMonitorSchema, 59 - }) 59 + }), 60 60 ), 61 61 maintenancesToPages: selectMaintenanceSchema.array().default([]), 62 62 }); ··· 85 85 monitorId: z.number(), 86 86 statusReportId: z.number(), 87 87 monitor: selectPublicMonitorSchema, 88 - }) 88 + }), 89 89 ) 90 90 .default([]), 91 91 statusReportUpdates: z.array(selectStatusReportUpdateSchema),
+9 -9
packages/db/src/schema/status_reports/status_reports.ts
··· 27 27 pageId: integer("page_id").references(() => page.id), 28 28 29 29 createdAt: integer("created_at", { mode: "timestamp" }).default( 30 - sql`(strftime('%s', 'now'))` 30 + sql`(strftime('%s', 'now'))`, 31 31 ), 32 32 updatedAt: integer("updated_at", { mode: "timestamp" }).default( 33 - sql`(strftime('%s', 'now'))` 33 + sql`(strftime('%s', 'now'))`, 34 34 ), 35 35 }); 36 36 ··· 45 45 .references(() => statusReport.id, { onDelete: "cascade" }) 46 46 .notNull(), 47 47 createdAt: integer("created_at", { mode: "timestamp" }).default( 48 - sql`(strftime('%s', 'now'))` 48 + sql`(strftime('%s', 'now'))`, 49 49 ), 50 50 updatedAt: integer("updated_at", { mode: "timestamp" }).default( 51 - sql`(strftime('%s', 'now'))` 51 + sql`(strftime('%s', 'now'))`, 52 52 ), 53 53 }); 54 54 ··· 65 65 fields: [statusReport.workspaceId], 66 66 references: [workspace.id], 67 67 }), 68 - }) 68 + }), 69 69 ); 70 70 71 71 export const statusReportUpdateRelations = relations( ··· 75 75 fields: [statusReportUpdate.statusReportId], 76 76 references: [statusReport.id], 77 77 }), 78 - }) 78 + }), 79 79 ); 80 80 81 81 export const monitorsToStatusReport = sqliteTable( ··· 88 88 .notNull() 89 89 .references(() => statusReport.id, { onDelete: "cascade" }), 90 90 createdAt: integer("created_at", { mode: "timestamp" }).default( 91 - sql`(strftime('%s', 'now'))` 91 + sql`(strftime('%s', 'now'))`, 92 92 ), 93 93 }, 94 94 (t) => ({ 95 95 pk: primaryKey(t.monitorId, t.statusReportId), 96 - }) 96 + }), 97 97 ); 98 98 99 99 export const monitorsToStatusReportRelations = relations( ··· 107 107 fields: [monitorsToStatusReport.statusReportId], 108 108 references: [statusReport.id], 109 109 }), 110 - }) 110 + }), 111 111 ); 112 112 113 113 // FIXME: We might have to drop foreign key constraints for the following tables
+2 -2
packages/db/src/schema/status_reports/validation.ts
··· 13 13 statusReportUpdate, 14 14 { 15 15 status: statusReportStatusSchema, 16 - } 16 + }, 17 17 ); 18 18 19 19 export const insertStatusReportSchema = createInsertSchema(statusReport, { ··· 41 41 statusReportUpdate, 42 42 { 43 43 status: statusReportStatusSchema, 44 - } 44 + }, 45 45 ); 46 46 47 47 export type InsertStatusReport = z.infer<typeof insertStatusReportSchema>;
+2 -2
packages/db/src/schema/workspaces/validation.ts
··· 1 1 import { createSelectSchema } from "drizzle-zod"; 2 2 import { z } from "zod"; 3 3 4 + import { allPlans } from "../plan/config"; 5 + import { limitsV1 } from "../plan/schema"; 4 6 import { workspacePlans, workspaceRole } from "./constants"; 5 7 import { workspace } from "./workspace"; 6 - import { limitsV1 } from "../plan/schema"; 7 - import { allPlans } from "../plan/config"; 8 8 9 9 export const workspacePlanSchema = z.enum(workspacePlans); 10 10 export const workspaceRoleSchema = z.enum(workspaceRole);
+3 -3
packages/db/src/schema/workspaces/workspace.ts
··· 19 19 paidUntil: integer("paid_until", { mode: "timestamp" }), 20 20 limits: text("limits").default("{}").notNull(), 21 21 createdAt: integer("created_at", { mode: "timestamp" }).default( 22 - sql`(strftime('%s', 'now'))` 22 + sql`(strftime('%s', 'now'))`, 23 23 ), 24 24 updatedAt: integer("updated_at", { mode: "timestamp" }).default( 25 - sql`(strftime('%s', 'now'))` 25 + sql`(strftime('%s', 'now'))`, 26 26 ), 27 27 28 28 dsn: text("dsn"), // should be removed soon 29 29 }, 30 30 (t) => ({ 31 31 unique: unique().on(t.id, t.dsn), 32 - }) 32 + }), 33 33 ); 34 34 35 35 export const workspaceRelations = relations(workspace, ({ many }) => ({
+1 -1
packages/db/src/seed.mts
··· 20 20 21 21 async function main() { 22 22 const db = drizzle( 23 - createClient({ url: env.DATABASE_URL, authToken: env.DATABASE_AUTH_TOKEN }) 23 + createClient({ url: env.DATABASE_URL, authToken: env.DATABASE_AUTH_TOKEN }), 24 24 ); 25 25 console.log("Seeding database "); 26 26 await db
+4 -5
packages/notifications/discord/src/index.ts
··· 20 20 notification, 21 21 statusCode, 22 22 message, 23 - incidentId, 24 23 }: { 25 24 monitor: Monitor; 26 25 notification: Notification; ··· 39 38 Your monitor with url ${monitor.url} is down with ${ 40 39 statusCode ? `status code ${statusCode}` : `error message ${message}` 41 40 }.`, 42 - webhookUrl 41 + webhookUrl, 43 42 ); 44 43 } catch (err) { 45 44 console.error(err); ··· 70 69 try { 71 70 await postToWebhook( 72 71 `Your monitor ${name}|${monitor.url} is up again πŸŽ‰`, 73 - webhookUrl 72 + webhookUrl, 74 73 ); 75 74 } catch (err) { 76 75 console.error(err); ··· 101 100 try { 102 101 await postToWebhook( 103 102 `Your monitor ${name}|${monitor.url} is degraded ⚠️`, 104 - webhookUrl 103 + webhookUrl, 105 104 ); 106 105 } catch (err) { 107 106 console.error(err); ··· 116 115 try { 117 116 await postToWebhook( 118 117 "This is a test notification from OpenStatus. \nIf you see this, it means that your webhook is working! πŸŽ‰", 119 - webhookUrl 118 + webhookUrl, 120 119 ); 121 120 return true; 122 121 } catch (_err) {
-1
packages/notifications/pagerduty/src/index.ts
··· 59 59 notification, 60 60 statusCode, 61 61 message, 62 - incidentId, 63 62 }: { 64 63 monitor: Monitor; 65 64 notification: Notification;
+4 -4
packages/notifications/pagerduty/src/schema/config.ts
··· 7 7 name: z.string(), 8 8 id: z.string(), 9 9 type: z.string(), 10 - }) 10 + }), 11 11 ), 12 12 account: z.object({ subdomain: z.string(), name: z.string() }), 13 13 }); ··· 56 56 }), 57 57 images: z.array(imageSchema).optional(), 58 58 links: z.array(linkSchema).optional(), 59 - }) 59 + }), 60 60 ); 61 61 62 62 export const acknowledgeEventPayloadSchema = baseEventPayloadSchema.merge( 63 63 z.object({ 64 64 event_action: z.literal("acknowledge"), 65 - }) 65 + }), 66 66 ); 67 67 68 68 export const resolveEventPayloadSchema = baseEventPayloadSchema.merge( 69 69 z.object({ 70 70 event_action: z.literal("resolve"), 71 - }) 71 + }), 72 72 ); 73 73 74 74 export const eventPayloadV2Schema = z.discriminatedUnion("event_action", [
+4 -4
packages/notifications/slack/src/index.ts
··· 58 58 }, 59 59 ], 60 60 }, 61 - webhookUrl 61 + webhookUrl, 62 62 ); 63 63 } catch (err) { 64 64 console.log(err); ··· 110 110 }, 111 111 ], 112 112 }, 113 - webhookUrl 113 + webhookUrl, 114 114 ); 115 115 } catch (err) { 116 116 console.log(err); ··· 159 159 }, 160 160 ], 161 161 }, 162 - webhookUrl 162 + webhookUrl, 163 163 ); 164 164 } catch (err) { 165 165 console.log(err); ··· 181 181 }, 182 182 ], 183 183 }, 184 - webhookUrl 184 + webhookUrl, 185 185 ); 186 186 return true; 187 187 } catch (_err) {
+10 -11
packages/notifications/twillio-sms/src/index.ts
··· 18 18 incidentId?: string; 19 19 }) => { 20 20 const notificationData = SmsConfigurationSchema.parse( 21 - JSON.parse(notification.data) 21 + JSON.parse(notification.data), 22 22 ); 23 23 const { name } = monitor; 24 24 ··· 29 29 "Body", 30 30 `Your monitor ${name} / ${monitor.url} is down with ${ 31 31 statusCode ? `status code ${statusCode}` : `error: ${message}` 32 - }` 32 + }`, 33 33 ); 34 34 35 35 try { ··· 40 40 body, 41 41 headers: { 42 42 Authorization: `Basic ${btoa( 43 - `${env.TWILLIO_ACCOUNT_ID}:${env.TWILLIO_AUTH_TOKEN}` 43 + `${env.TWILLIO_ACCOUNT_ID}:${env.TWILLIO_AUTH_TOKEN}`, 44 44 )}`, 45 45 }, 46 - } 46 + }, 47 47 ); 48 48 } catch (err) { 49 49 console.log(err); ··· 68 68 incidentId?: string; 69 69 }) => { 70 70 const notificationData = SmsConfigurationSchema.parse( 71 - JSON.parse(notification.data) 71 + JSON.parse(notification.data), 72 72 ); 73 73 const { name } = monitor; 74 74 ··· 85 85 body, 86 86 headers: { 87 87 Authorization: `Basic ${btoa( 88 - `${env.TWILLIO_ACCOUNT_ID}:${env.TWILLIO_AUTH_TOKEN}` 88 + `${env.TWILLIO_ACCOUNT_ID}:${env.TWILLIO_AUTH_TOKEN}`, 89 89 )}`, 90 90 }, 91 - } 91 + }, 92 92 ); 93 93 } catch (err) { 94 94 console.log(err); ··· 103 103 statusCode, 104 104 // biome-ignore lint/correctness/noUnusedVariables: <explanation> 105 105 message, 106 - incidentId, 107 106 }: { 108 107 monitor: Monitor; 109 108 notification: Notification; ··· 112 111 incidentId?: string; 113 112 }) => { 114 113 const notificationData = SmsConfigurationSchema.parse( 115 - JSON.parse(notification.data) 114 + JSON.parse(notification.data), 116 115 ); 117 116 const { name } = monitor; 118 117 ··· 129 128 body, 130 129 headers: { 131 130 Authorization: `Basic ${btoa( 132 - `${env.TWILLIO_ACCOUNT_ID}:${env.TWILLIO_AUTH_TOKEN}` 131 + `${env.TWILLIO_ACCOUNT_ID}:${env.TWILLIO_AUTH_TOKEN}`, 133 132 )}`, 134 133 }, 135 - } 134 + }, 136 135 ); 137 136 } catch (err) { 138 137 console.log(err);
+2 -8
packages/react/package.json
··· 5 5 "type": "git", 6 6 "url": "https://github.com/openstatusHQ/openstatus.git" 7 7 }, 8 - "keywords": [ 9 - "openstatus", 10 - "react", 11 - "monitoring" 12 - ], 8 + "keywords": ["openstatus", "react", "monitoring"], 13 9 "homepage": "https://github.com/openstatusHQ/openstatus#readme", 14 10 "main": "./dist/index.js", 15 11 "types": "./dist/index.d.ts", 16 12 "module": "./dist/index.mjs", 17 - "files": [ 18 - "./dist/**" 19 - ], 13 + "files": ["./dist/**"], 20 14 "license": "MIT", 21 15 "scripts": { 22 16 "dev": "tsup --watch && pnpm run dev:tailwind",
+3 -3
packages/tinybird/src/os-client.ts
··· 118 118 opts?: { 119 119 cache?: RequestCache | undefined; 120 120 revalidate: number | undefined; 121 - } // RETHINK: not the best way to handle it 121 + }, // RETHINK: not the best way to handle it 122 122 ) => { 123 123 try { 124 124 const res = await this.tb.buildPipe({ ··· 178 178 179 179 endpointStatusPeriod( 180 180 period: "7d" | "45d", 181 - timezone: "UTC" = "UTC" // "EST" | "PST" | "CET" 181 + timezone: "UTC" = "UTC", // "EST" | "PST" | "CET" 182 182 ) { 183 183 const parameters = z.object({ monitorId: z.string() }); 184 184 ··· 187 187 opts?: { 188 188 cache?: RequestCache | undefined; 189 189 revalidate: number | undefined; 190 - } // RETHINK: not the best way to handle it 190 + }, // RETHINK: not the best way to handle it 191 191 ) => { 192 192 try { 193 193 const res = await this.tb.buildPipe({
+1 -1
packages/utils/index.ts
··· 392 392 Oceania: [], 393 393 Asia: [], 394 394 Africa: [], 395 - } 395 + }, 396 396 ); 397 397 398 398 export const vercelRegions = [