Openstatus www.openstatus.dev

๐Ÿš€ monitor (#42)

authored by

Thibault Le Ouay and committed by
GitHub
9beb5c70 c6c4d741

+155 -18
+9 -2
apps/web/src/app/app/(dashboard)/[workspaceId]/incident/page.tsx
··· 3 3 import { Container } from "@/components/dashboard/container"; 4 4 import { Header } from "@/components/dashboard/header"; 5 5 import { wait } from "@/lib/utils"; 6 + import { api } from "@/trpc/server"; 6 7 7 - export default async function IncidentPage() { 8 - await wait(1000); 8 + export default async function IncidentPage({ 9 + params, 10 + }: { 11 + params: { workspaceId: string }; 12 + }) { 13 + const incidents = await api.incident.getIncidentByWorkspace.query({ 14 + workspaceId: Number(params.workspaceId), 15 + }); 9 16 return ( 10 17 <div className="grid gap-6 md:grid-cols-2 md:gap-8"> 11 18 <Header title="Monitor" description="Overview of all the responses." />
+11 -3
apps/web/src/app/app/(dashboard)/[workspaceId]/monitor/page.tsx
··· 2 2 3 3 import { Container } from "@/components/dashboard/container"; 4 4 import { Header } from "@/components/dashboard/header"; 5 - import { wait } from "@/lib/utils"; 5 + import { api } from "@/trpc/server"; 6 6 7 - export default async function MonitorPage() { 8 - await wait(1000); 7 + export default async function MonitorPage({ 8 + params, 9 + }: { 10 + params: { workspaceId: string }; 11 + }) { 12 + const monitors = await api.monitor.getMonitorsByWorkspace.query({ 13 + workspaceId: Number(params.workspaceId), 14 + }); 15 + console.log(monitors); 16 + // iterate over monitors 9 17 return ( 10 18 <div className="grid gap-6 md:grid-cols-2 md:gap-8"> 11 19 <Header title="Monitor" description="Overview of all the responses." />
+7 -1
apps/web/src/app/app/(dashboard)/[workspaceId]/page.tsx
··· 5 5 import { DialogForm } from "@/components/forms/dialog-form"; 6 6 import { Button } from "@/components/ui/button"; 7 7 import { wait } from "@/lib/utils"; 8 + import { api } from "@/trpc/server"; 9 + import Loading from "./loading"; 8 10 9 11 export default async function DashboardPage() { 10 - await wait(1000); 12 + const workspace = await api.workspace.getUserWithWorkspace.query(); 13 + if (!workspace) { 14 + return <Loading />; 15 + } 16 + 11 17 return ( 12 18 <div className="grid gap-6 md:grid-cols-2 md:gap-8"> 13 19 <div className="col-span-full flex w-full justify-between">
+13
apps/web/src/app/app/(dashboard)/[workspaceId]/page/loading.tsx
··· 1 + import { Container } from "@/components/dashboard/container"; 2 + import { Header } from "@/components/dashboard/header"; 3 + 4 + export default function Loading() { 5 + return ( 6 + <div className="grid gap-6 md:grid-cols-2 md:gap-8"> 7 + <Header.Skeleton /> 8 + <Container.Skeleton /> 9 + <Container.Skeleton /> 10 + <Container.Skeleton /> 11 + </div> 12 + ); 13 + }
+27
apps/web/src/app/app/(dashboard)/[workspaceId]/page/page.tsx
··· 1 + import * as React from "react"; 2 + 3 + import { Container } from "@/components/dashboard/container"; 4 + import { Header } from "@/components/dashboard/header"; 5 + import { wait } from "@/lib/utils"; 6 + import { api } from "@/trpc/server"; 7 + 8 + export default async function Page({ 9 + params, 10 + }: { 11 + params: { workspaceId: string }; 12 + }) { 13 + const pages = await api.page.getPageByWorkspace.query({ 14 + workspaceId: Number(params.workspaceId), 15 + }); 16 + // iterate over pages 17 + return ( 18 + <div className="grid gap-6 md:grid-cols-2 md:gap-8"> 19 + <Header 20 + title="Status Page" 21 + description="Overview of all your status page." 22 + /> 23 + <Container title="Hello"></Container> 24 + <Container title="World"></Container> 25 + </div> 26 + ); 27 + }
+2 -1
apps/web/src/components/icons.tsx
··· 1 - import { Activity, LayoutDashboard, Link, Siren } from "lucide-react"; 1 + import { Activity, LayoutDashboard, Link, PanelTop, Siren } from "lucide-react"; 2 2 import type { Icon as LucideIcon, LucideProps } from "lucide-react"; 3 3 4 4 export type Icon = LucideIcon; ··· 9 9 "layout-dashboard": LayoutDashboard, 10 10 link: Link, 11 11 siren: Siren, 12 + "panel-top": PanelTop, 12 13 } as const;
+9 -3
apps/web/src/config/pages.ts
··· 16 16 icon: "layout-dashboard", 17 17 }, 18 18 { 19 - title: "Endpoint", 19 + title: "Endpoints", 20 20 description: "Keep track of all your endpoints.", 21 21 href: "/endpoint", 22 22 icon: "link", 23 23 }, 24 24 { 25 - title: "Monitor", 25 + title: "Monitors", 26 26 description: "Check all the responses in one place.", 27 27 href: "/monitor", 28 28 icon: "activity", 29 29 }, 30 30 { 31 - title: "Incident", 31 + title: "Pages", 32 + description: "Wher you can see all the pages.", 33 + href: "/page", 34 + icon: "panel-top", 35 + }, 36 + { 37 + title: "Incidents", 32 38 description: "War room where you handle the incidents.", 33 39 href: "/incident", 34 40 icon: "siren",
+12 -2
apps/web/src/middleware.ts
··· 28 28 } 29 29 30 30 // redirect them to organization selection page 31 - if (auth.userId && req.nextUrl.pathname === "/app") { 31 + if ( 32 + auth.userId && 33 + (req.nextUrl.pathname === "/app" || req.nextUrl.pathname === "/app/") 34 + ) { 35 + console.log(auth.userId); 36 + // improve on sign-up if the webhook has not been triggered yet 32 37 const userQuery = db 33 38 .select() 34 39 .from(user) ··· 37 42 const result = await db 38 43 .select() 39 44 .from(usersToWorkspaces) 40 - .leftJoin(userQuery, eq(userQuery.id, usersToWorkspaces.userId)) 45 + .innerJoin(userQuery, eq(userQuery.id, usersToWorkspaces.userId)) 41 46 .execute(); 47 + 42 48 if (result.length) { 43 49 const orgSelection = new URL( 44 50 `/app/${result[0].users_to_workspaces.workspaceId}`, 45 51 req.url, 46 52 ); 47 53 return NextResponse.redirect(orgSelection); 54 + } else { 55 + // return NextResponse.redirect(new URL("/app/onboarding", req.url)); 56 + // probably redirect to onboarding 57 + // or find a way to wait for the webhook 48 58 } 49 59 } 50 60 },
+15
packages/api/src/router/incident.ts
··· 6 6 incidentUpdate, 7 7 insertIncidentSchema, 8 8 insertIncidentUpdateSchema, 9 + page, 9 10 } from "@openstatus/db/src/schema"; 10 11 11 12 import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; ··· 35 36 .update(incident) 36 37 .set(opts.input.status) 37 38 .where(eq(incident.id, opts.input.incidentId)) 39 + .execute(); 40 + }), 41 + getIncidentByWorkspace: protectedProcedure 42 + .input(z.object({ workspaceId: z.number() })) 43 + .query(async (opts) => { 44 + const pageQuery = opts.ctx.db 45 + .select() 46 + .from(page) 47 + .where(eq(page.workspaceId, opts.input.workspaceId)) 48 + .as("pageQuery"); 49 + return opts.ctx.db 50 + .select() 51 + .from(incident) 52 + .innerJoin(pageQuery, eq(incident.pageId, pageQuery.id)) 38 53 .execute(); 39 54 }), 40 55 });
+9
packages/api/src/router/monitor.ts
··· 57 57 .delete(monitor) 58 58 .where(eq(monitor.id, opts.input.monitorId)); 59 59 }), 60 + getMonitorsByWorkspace: protectedProcedure 61 + .input(z.object({ workspaceId: z.number() })) 62 + .query(async (opts) => { 63 + return await opts.ctx.db 64 + .select() 65 + .from(monitor) 66 + .where(eq(monitor.workspaceId, opts.input.workspaceId)) 67 + .execute(); 68 + }), 60 69 });
+8
packages/api/src/router/page.ts
··· 22 22 }) 23 23 .execute(); 24 24 }), 25 + getPageByWorkspace: protectedProcedure 26 + .input(z.object({ workspaceId: z.number() })) 27 + .query(async (opts) => { 28 + return await opts.ctx.db 29 + .select() 30 + .from(page) 31 + .where(eq(page.workspaceId, opts.input.workspaceId)); 32 + }), 25 33 // public if we use trpc hooks to get the page from the url 26 34 getPageBySlug: publicProcedure 27 35 .input(z.object({ slug: z.string() }))
+12 -5
packages/api/src/router/workspace.ts
··· 1 1 import { z } from "zod"; 2 2 3 3 import { eq } from "@openstatus/db"; 4 - import { page, user, workspace } from "@openstatus/db/src/schema"; 4 + import { 5 + page, 6 + user, 7 + usersToWorkspaces, 8 + workspace, 9 + } from "@openstatus/db/src/schema"; 5 10 6 11 import { createTRPCRouter, protectedProcedure } from "../trpc"; 7 12 8 13 export const workspaceRouter = createTRPCRouter({ 9 - getUserWorkspace: protectedProcedure.query(async (opts) => { 10 - return await opts.ctx.db.query.workspace.findMany({ 14 + getUserWithWorkspace: protectedProcedure.query(async (opts) => { 15 + return await opts.ctx.db.query.user.findMany({ 11 16 with: { 12 17 usersToWorkspaces: { 13 - where: eq(user.tenantId, opts.ctx.auth.userId), 14 - with: { user: true }, 18 + with: { 19 + workspace: true, 20 + }, 15 21 }, 16 22 }, 23 + where: eq(user.tenantId, opts.ctx.auth.userId), 17 24 }); 18 25 }), 19 26 getWorkspace: protectedProcedure
+7 -1
packages/db/src/schema/monitor.ts
··· 10 10 import { createInsertSchema, createSelectSchema } from "drizzle-zod"; 11 11 12 12 import { page } from "./page"; 13 + import { workspace } from "./workspace"; 13 14 14 15 export const monitor = mysqlTable("monitor", { 15 16 id: int("id").autoincrement().primaryKey(), ··· 35 36 name: varchar("name", { length: 256 }), 36 37 description: text("description"), 37 38 38 - pageId: int("page_id").notNull(), 39 + pageId: int("page_id"), 40 + workspaceId: int("workspace_id"), 39 41 40 42 createdAt: timestamp("created_at").notNull().defaultNow(), 41 43 updateddAt: timestamp("updated_at").notNull().defaultNow().onUpdateNow(), ··· 45 47 page: one(page, { 46 48 fields: [monitor.pageId], 47 49 references: [page.id], 50 + }), 51 + workspace: one(workspace, { 52 + fields: [monitor.workspaceId], 53 + references: [workspace.id], 48 54 }), 49 55 })); 50 56
+14
packages/db/src/schema/user.ts
··· 31 31 pk: primaryKey(t.userId, t.workspaceId), 32 32 }), 33 33 ); 34 + 35 + export const usersToWorkspaceRelations = relations( 36 + usersToWorkspaces, 37 + ({ one }) => ({ 38 + workspace: one(workspace, { 39 + fields: [usersToWorkspaces.workspaceId], 40 + references: [workspace.id], 41 + }), 42 + user: one(user, { 43 + fields: [usersToWorkspaces.userId], 44 + references: [user.id], 45 + }), 46 + }), 47 + );