db: add monitoring schema and sample seed data
This commit is contained in:
parent
33e75c056f
commit
c7edd90a7e
8 changed files with 1689 additions and 0 deletions
|
|
@ -14,12 +14,14 @@
|
||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:studio": "drizzle-kit studio",
|
"db:studio": "drizzle-kit studio",
|
||||||
"db:migrate": "drizzle-kit migrate",
|
"db:migrate": "drizzle-kit migrate",
|
||||||
|
"db:seed": "bun --env-file=../../apps/server/.env run ./src/seed.ts",
|
||||||
"db:start": "docker compose up -d",
|
"db:start": "docker compose up -d",
|
||||||
"db:watch": "docker compose up",
|
"db:watch": "docker compose up",
|
||||||
"db:stop": "docker compose stop",
|
"db:stop": "docker compose stop",
|
||||||
"db:down": "docker compose down"
|
"db:down": "docker compose down"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"better-auth": "catalog:",
|
||||||
"@minmon/env": "workspace:*",
|
"@minmon/env": "workspace:*",
|
||||||
"dotenv": "catalog:",
|
"dotenv": "catalog:",
|
||||||
"drizzle-orm": "^0.45.1",
|
"drizzle-orm": "^0.45.1",
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import { drizzle } from "drizzle-orm/node-postgres";
|
||||||
|
|
||||||
import * as schema from "./schema";
|
import * as schema from "./schema";
|
||||||
|
|
||||||
|
export * from "./schema";
|
||||||
|
|
||||||
export function createDb() {
|
export function createDb() {
|
||||||
return drizzle(env.DATABASE_URL, { schema });
|
return drizzle(env.DATABASE_URL, { schema });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
136
packages/db/src/migrations/0000_thick_molecule_man.sql
Normal file
136
packages/db/src/migrations/0000_thick_molecule_man.sql
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
CREATE TYPE "public"."domain_resolution_status" AS ENUM('pending', 'resolved', 'mismatch', 'unreachable');--> statement-breakpoint
|
||||||
|
CREATE TYPE "public"."note_category" AS ENUM('general', 'maintenance', 'incident', 'todo');--> statement-breakpoint
|
||||||
|
CREATE TYPE "public"."server_environment" AS ENUM('development', 'staging', 'production');--> statement-breakpoint
|
||||||
|
CREATE TYPE "public"."server_status" AS ENUM('healthy', 'warning', 'critical', 'offline');--> statement-breakpoint
|
||||||
|
CREATE TYPE "public"."service_status" AS ENUM('running', 'degraded', 'stopped', 'maintenance');--> statement-breakpoint
|
||||||
|
CREATE TYPE "public"."service_type" AS ENUM('frontend', 'backend', 'database', 'worker', 'proxy', 'monitoring', 'other');--> statement-breakpoint
|
||||||
|
CREATE TABLE "account" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"account_id" text NOT NULL,
|
||||||
|
"provider_id" text NOT NULL,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"access_token" text,
|
||||||
|
"refresh_token" text,
|
||||||
|
"id_token" text,
|
||||||
|
"access_token_expires_at" timestamp,
|
||||||
|
"refresh_token_expires_at" timestamp,
|
||||||
|
"scope" text,
|
||||||
|
"password" text,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "session" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"expires_at" timestamp NOT NULL,
|
||||||
|
"token" text NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp NOT NULL,
|
||||||
|
"ip_address" text,
|
||||||
|
"user_agent" text,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
CONSTRAINT "session_token_unique" UNIQUE("token")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "user" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"email" text NOT NULL,
|
||||||
|
"email_verified" boolean DEFAULT false NOT NULL,
|
||||||
|
"image" text,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "user_email_unique" UNIQUE("email")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "verification" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"identifier" text NOT NULL,
|
||||||
|
"value" text NOT NULL,
|
||||||
|
"expires_at" timestamp NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "domain" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"server_id" text NOT NULL,
|
||||||
|
"service_id" text,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"expected_server_ip" text NOT NULL,
|
||||||
|
"last_resolved_ipv4" text,
|
||||||
|
"last_resolved_ipv6" text,
|
||||||
|
"resolution_status" "domain_resolution_status" DEFAULT 'pending' NOT NULL,
|
||||||
|
"last_checked_at" timestamp,
|
||||||
|
"remarks" text,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "note" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"server_id" text,
|
||||||
|
"service_id" text,
|
||||||
|
"domain_id" text,
|
||||||
|
"title" text NOT NULL,
|
||||||
|
"content" text NOT NULL,
|
||||||
|
"category" "note_category" DEFAULT 'general' NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "server" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"host" text NOT NULL,
|
||||||
|
"ip_address" text NOT NULL,
|
||||||
|
"provider" text,
|
||||||
|
"region" text,
|
||||||
|
"environment" "server_environment" DEFAULT 'production' NOT NULL,
|
||||||
|
"status" "server_status" DEFAULT 'healthy' NOT NULL,
|
||||||
|
"operating_system" text,
|
||||||
|
"remarks" text,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "service" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"server_id" text NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"type" "service_type" DEFAULT 'other' NOT NULL,
|
||||||
|
"status" "service_status" DEFAULT 'running' NOT NULL,
|
||||||
|
"port" text,
|
||||||
|
"version" text,
|
||||||
|
"health_check_path" text,
|
||||||
|
"remarks" text,
|
||||||
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "domain" ADD CONSTRAINT "domain_server_id_server_id_fk" FOREIGN KEY ("server_id") REFERENCES "public"."server"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "domain" ADD CONSTRAINT "domain_service_id_service_id_fk" FOREIGN KEY ("service_id") REFERENCES "public"."service"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "note" ADD CONSTRAINT "note_server_id_server_id_fk" FOREIGN KEY ("server_id") REFERENCES "public"."server"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "note" ADD CONSTRAINT "note_service_id_service_id_fk" FOREIGN KEY ("service_id") REFERENCES "public"."service"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "note" ADD CONSTRAINT "note_domain_id_domain_id_fk" FOREIGN KEY ("domain_id") REFERENCES "public"."domain"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "service" ADD CONSTRAINT "service_server_id_server_id_fk" FOREIGN KEY ("server_id") REFERENCES "public"."server"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
CREATE INDEX "account_userId_idx" ON "account" USING btree ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "session_userId_idx" ON "session" USING btree ("user_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "verification_identifier_idx" ON "verification" USING btree ("identifier");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "domain_name_unique" ON "domain" USING btree ("name");--> statement-breakpoint
|
||||||
|
CREATE INDEX "domain_serverId_idx" ON "domain" USING btree ("server_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "domain_serviceId_idx" ON "domain" USING btree ("service_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "domain_resolutionStatus_idx" ON "domain" USING btree ("resolution_status");--> statement-breakpoint
|
||||||
|
CREATE INDEX "note_serverId_idx" ON "note" USING btree ("server_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "note_serviceId_idx" ON "note" USING btree ("service_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "note_domainId_idx" ON "note" USING btree ("domain_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "note_category_idx" ON "note" USING btree ("category");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "server_host_unique" ON "server" USING btree ("host");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "server_ipAddress_unique" ON "server" USING btree ("ip_address");--> statement-breakpoint
|
||||||
|
CREATE INDEX "server_environment_idx" ON "server" USING btree ("environment");--> statement-breakpoint
|
||||||
|
CREATE INDEX "server_status_idx" ON "server" USING btree ("status");--> statement-breakpoint
|
||||||
|
CREATE INDEX "service_serverId_idx" ON "service" USING btree ("server_id");--> statement-breakpoint
|
||||||
|
CREATE INDEX "service_status_idx" ON "service" USING btree ("status");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "service_serverId_name_unique" ON "service" USING btree ("server_id","name");
|
||||||
1089
packages/db/src/migrations/meta/0000_snapshot.json
Normal file
1089
packages/db/src/migrations/meta/0000_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
13
packages/db/src/migrations/meta/_journal.json
Normal file
13
packages/db/src/migrations/meta/_journal.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1777458791180,
|
||||||
|
"tag": "0000_thick_molecule_man",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
export * from "./auth";
|
export * from "./auth";
|
||||||
|
export * from "./monitoring";
|
||||||
export {};
|
export {};
|
||||||
|
|
|
||||||
168
packages/db/src/schema/monitoring.ts
Normal file
168
packages/db/src/schema/monitoring.ts
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
import { relations, sql } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
index,
|
||||||
|
integer,
|
||||||
|
jsonb,
|
||||||
|
pgEnum,
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
uniqueIndex,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
export const serverStatusEnum = pgEnum("server_status", ["active", "maintenance", "inactive"]);
|
||||||
|
|
||||||
|
export const serviceTypeEnum = pgEnum("service_type", [
|
||||||
|
"web_app",
|
||||||
|
"mail",
|
||||||
|
"database",
|
||||||
|
"api",
|
||||||
|
"proxy",
|
||||||
|
"other",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const serviceProtocolEnum = pgEnum("service_protocol", [
|
||||||
|
"http",
|
||||||
|
"https",
|
||||||
|
"tcp",
|
||||||
|
"udp",
|
||||||
|
"other",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const serviceStatusEnum = pgEnum("service_status", ["active", "down", "maintenance"]);
|
||||||
|
|
||||||
|
export const domainResolutionStatusEnum = pgEnum("domain_resolution_status", [
|
||||||
|
"MATCH",
|
||||||
|
"MISMATCH",
|
||||||
|
"UNRESOLVED",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const server = pgTable(
|
||||||
|
"server",
|
||||||
|
{
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
primaryIpAddress: text("primary_ip_address").notNull(),
|
||||||
|
secondaryIpAddress: text("secondary_ip_address"),
|
||||||
|
operatingSystem: text("operating_system"),
|
||||||
|
location: text("location"),
|
||||||
|
provider: text("provider"),
|
||||||
|
description: text("description"),
|
||||||
|
status: serverStatusEnum("status").default("active").notNull(),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updated_at")
|
||||||
|
.defaultNow()
|
||||||
|
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||||
|
.notNull(),
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
uniqueIndex("server_name_unique").on(table.name),
|
||||||
|
index("server_status_idx").on(table.status),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const service = pgTable(
|
||||||
|
"service",
|
||||||
|
{
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
serverId: text("server_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => server.id, { onDelete: "cascade" }),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
type: serviceTypeEnum("type").default("other").notNull(),
|
||||||
|
internalPort: integer("internal_port"),
|
||||||
|
externalPort: integer("external_port"),
|
||||||
|
protocol: serviceProtocolEnum("protocol").default("http").notNull(),
|
||||||
|
status: serviceStatusEnum("status").default("active").notNull(),
|
||||||
|
description: text("description"),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updated_at")
|
||||||
|
.defaultNow()
|
||||||
|
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||||
|
.notNull(),
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
index("service_server_id_idx").on(table.serverId),
|
||||||
|
index("service_status_idx").on(table.status),
|
||||||
|
uniqueIndex("service_server_name_unique").on(table.serverId, table.name),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const domain = pgTable(
|
||||||
|
"domain",
|
||||||
|
{
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
serviceId: text("service_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => service.id, { onDelete: "cascade" }),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
expectedServerIp: text("expected_server_ip").notNull(),
|
||||||
|
lastResolvedIp: text("last_resolved_ip"),
|
||||||
|
lastResolvedIps: jsonb("last_resolved_ips")
|
||||||
|
.$type<string[]>()
|
||||||
|
.default(sql`'[]'::jsonb`)
|
||||||
|
.notNull(),
|
||||||
|
resolutionStatus: domainResolutionStatusEnum("resolution_status")
|
||||||
|
.default("UNRESOLVED")
|
||||||
|
.notNull(),
|
||||||
|
lastCheckedAt: timestamp("last_checked_at"),
|
||||||
|
remarks: text("remarks"),
|
||||||
|
lastCheckMessage: text("last_check_message"),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updated_at")
|
||||||
|
.defaultNow()
|
||||||
|
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||||
|
.notNull(),
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
uniqueIndex("domain_name_unique").on(table.name),
|
||||||
|
index("domain_service_id_idx").on(table.serviceId),
|
||||||
|
index("domain_resolution_status_idx").on(table.resolutionStatus),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const note = pgTable(
|
||||||
|
"note",
|
||||||
|
{
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
serverId: text("server_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => server.id, { onDelete: "cascade" }),
|
||||||
|
title: text("title").notNull(),
|
||||||
|
content: text("content").notNull(),
|
||||||
|
category: text("category"),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updated_at")
|
||||||
|
.defaultNow()
|
||||||
|
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||||
|
.notNull(),
|
||||||
|
},
|
||||||
|
(table) => [index("note_server_id_idx").on(table.serverId), index("note_category_idx").on(table.category)],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const serverRelations = relations(server, ({ many }) => ({
|
||||||
|
services: many(service),
|
||||||
|
notes: many(note),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const serviceRelations = relations(service, ({ one, many }) => ({
|
||||||
|
server: one(server, {
|
||||||
|
fields: [service.serverId],
|
||||||
|
references: [server.id],
|
||||||
|
}),
|
||||||
|
domains: many(domain),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const domainRelations = relations(domain, ({ one }) => ({
|
||||||
|
service: one(service, {
|
||||||
|
fields: [domain.serviceId],
|
||||||
|
references: [service.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const noteRelations = relations(note, ({ one }) => ({
|
||||||
|
server: one(server, {
|
||||||
|
fields: [note.serverId],
|
||||||
|
references: [server.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
278
packages/db/src/seed.ts
Normal file
278
packages/db/src/seed.ts
Normal file
|
|
@ -0,0 +1,278 @@
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import { hashPassword } from "better-auth/crypto";
|
||||||
|
|
||||||
|
dotenv.config({
|
||||||
|
path: new URL("../../../apps/server/.env", import.meta.url).pathname,
|
||||||
|
});
|
||||||
|
|
||||||
|
import { db } from "./index";
|
||||||
|
import { account, user } from "./schema/auth";
|
||||||
|
import { domain, note, server, service } from "./schema/monitoring";
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const earlierToday = new Date(now.getTime() - 45 * 60 * 1000);
|
||||||
|
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
const adminUser = {
|
||||||
|
id: "user_admin_minmon",
|
||||||
|
name: "Minmon Admin",
|
||||||
|
email: "admin@minmon.local",
|
||||||
|
password: "admin12345",
|
||||||
|
};
|
||||||
|
|
||||||
|
const servers = [
|
||||||
|
{
|
||||||
|
id: "server_prod_1",
|
||||||
|
name: "Production VPS",
|
||||||
|
primaryIpAddress: "203.0.113.10",
|
||||||
|
secondaryIpAddress: "203.0.113.11",
|
||||||
|
operatingSystem: "Ubuntu 24.04 LTS",
|
||||||
|
location: "Singapore",
|
||||||
|
provider: "DigitalOcean",
|
||||||
|
description: "Main production server hosting the dashboard web app, API, and reverse proxy.",
|
||||||
|
status: "active" as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "server_mail_1",
|
||||||
|
name: "Mail Server",
|
||||||
|
primaryIpAddress: "198.51.100.20",
|
||||||
|
secondaryIpAddress: null,
|
||||||
|
operatingSystem: "Debian 12",
|
||||||
|
location: "Jakarta",
|
||||||
|
provider: "Hetzner",
|
||||||
|
description: "Handles the demo mail service and SMTP/IMAP examples.",
|
||||||
|
status: "maintenance" as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "server_lab_1",
|
||||||
|
name: "Campus Lab VM",
|
||||||
|
primaryIpAddress: "192.0.2.18",
|
||||||
|
secondaryIpAddress: null,
|
||||||
|
operatingSystem: "Ubuntu 22.04 LTS",
|
||||||
|
location: "Campus Lab",
|
||||||
|
provider: "University Lab",
|
||||||
|
description: "Used for student practice and lecturer demonstration in class.",
|
||||||
|
status: "inactive" as const,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const services = [
|
||||||
|
{
|
||||||
|
id: "service_dashboard_web",
|
||||||
|
serverId: "server_prod_1",
|
||||||
|
name: "Dashboard Web",
|
||||||
|
type: "web_app" as const,
|
||||||
|
internalPort: 3000,
|
||||||
|
externalPort: 443,
|
||||||
|
protocol: "https" as const,
|
||||||
|
status: "active" as const,
|
||||||
|
description: "Frontend application for the private server management dashboard.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "service_dashboard_api",
|
||||||
|
serverId: "server_prod_1",
|
||||||
|
name: "Dashboard API",
|
||||||
|
type: "api" as const,
|
||||||
|
internalPort: 3001,
|
||||||
|
externalPort: 443,
|
||||||
|
protocol: "https" as const,
|
||||||
|
status: "active" as const,
|
||||||
|
description: "Handles CRUD data, dashboard summary, and DNS checks.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "service_mail_gateway",
|
||||||
|
serverId: "server_mail_1",
|
||||||
|
name: "Mail Gateway",
|
||||||
|
type: "mail" as const,
|
||||||
|
internalPort: 25,
|
||||||
|
externalPort: 25,
|
||||||
|
protocol: "tcp" as const,
|
||||||
|
status: "maintenance" as const,
|
||||||
|
description: "Mail service used as a sample SMTP gateway for the assignment.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "service_lab_proxy",
|
||||||
|
serverId: "server_lab_1",
|
||||||
|
name: "Lab Reverse Proxy",
|
||||||
|
type: "proxy" as const,
|
||||||
|
internalPort: 80,
|
||||||
|
externalPort: 80,
|
||||||
|
protocol: "http" as const,
|
||||||
|
status: "down" as const,
|
||||||
|
description: "Reverse proxy for local practice deployments in the campus lab.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const domains = [
|
||||||
|
{
|
||||||
|
id: "domain_dashboard_web",
|
||||||
|
serviceId: "service_dashboard_web",
|
||||||
|
name: "dashboard.minmon.dev",
|
||||||
|
expectedServerIp: "203.0.113.10",
|
||||||
|
lastResolvedIp: "203.0.113.10",
|
||||||
|
lastResolvedIps: ["203.0.113.10"],
|
||||||
|
resolutionStatus: "MATCH" as const,
|
||||||
|
lastCheckedAt: earlierToday,
|
||||||
|
remarks: "Primary domain for the dashboard frontend.",
|
||||||
|
lastCheckMessage: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "domain_dashboard_api",
|
||||||
|
serviceId: "service_dashboard_api",
|
||||||
|
name: "api.minmon.dev",
|
||||||
|
expectedServerIp: "203.0.113.10",
|
||||||
|
lastResolvedIp: "203.0.113.15",
|
||||||
|
lastResolvedIps: ["203.0.113.15", "203.0.113.10"],
|
||||||
|
resolutionStatus: "MATCH" as const,
|
||||||
|
lastCheckedAt: earlierToday,
|
||||||
|
remarks: "API subdomain. Multiple A records still include the expected IP.",
|
||||||
|
lastCheckMessage: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "domain_mail_gateway",
|
||||||
|
serviceId: "service_mail_gateway",
|
||||||
|
name: "mail.minmon.dev",
|
||||||
|
expectedServerIp: "198.51.100.20",
|
||||||
|
lastResolvedIp: "198.51.100.77",
|
||||||
|
lastResolvedIps: ["198.51.100.77"],
|
||||||
|
resolutionStatus: "MISMATCH" as const,
|
||||||
|
lastCheckedAt: yesterday,
|
||||||
|
remarks: "DNS still points to the old mail gateway.",
|
||||||
|
lastCheckMessage: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "domain_lab_demo",
|
||||||
|
serviceId: "service_lab_proxy",
|
||||||
|
name: "lab.minmon.dev",
|
||||||
|
expectedServerIp: "192.0.2.18",
|
||||||
|
lastResolvedIp: null,
|
||||||
|
lastResolvedIps: [],
|
||||||
|
resolutionStatus: "UNRESOLVED" as const,
|
||||||
|
lastCheckedAt: yesterday,
|
||||||
|
remarks: "Lab VM is powered off outside teaching hours.",
|
||||||
|
lastCheckMessage: "DNS lookup failed because the host could not be resolved.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const notes = [
|
||||||
|
{
|
||||||
|
id: "note_prod_backup",
|
||||||
|
serverId: "server_prod_1",
|
||||||
|
title: "Weekly backup reminder",
|
||||||
|
content: "Verify the PostgreSQL backup every Friday before the lecturer demo session.",
|
||||||
|
category: "maintenance",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "note_prod_dataset",
|
||||||
|
serverId: "server_prod_1",
|
||||||
|
title: "Main sample dataset",
|
||||||
|
content:
|
||||||
|
"This production server and its services form the main student-facing sample dataset for CRUD and monitoring demos.",
|
||||||
|
category: "general",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "note_mail_dns",
|
||||||
|
serverId: "server_mail_1",
|
||||||
|
title: "Update stale A record",
|
||||||
|
content: "Point mail.minmon.dev to the current mail server before final presentation week.",
|
||||||
|
category: "todo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "note_lab_status",
|
||||||
|
serverId: "server_lab_1",
|
||||||
|
title: "Lab VM inactive",
|
||||||
|
content: "The campus lab VM is intentionally inactive outside class hours, so related checks may fail.",
|
||||||
|
category: "incident",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
async function seedAuthAdmin() {
|
||||||
|
const existingUser = await db.query.user.findFirst({
|
||||||
|
where: (table, { eq }) => eq(table.email, adminUser.email),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
return existingUser.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordHash = await hashPassword(adminUser.password);
|
||||||
|
|
||||||
|
await db.insert(user).values({
|
||||||
|
id: adminUser.id,
|
||||||
|
name: adminUser.name,
|
||||||
|
email: adminUser.email,
|
||||||
|
emailVerified: true,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.insert(account).values({
|
||||||
|
id: "account_admin_minmon",
|
||||||
|
accountId: adminUser.id,
|
||||||
|
providerId: "credential",
|
||||||
|
userId: adminUser.id,
|
||||||
|
password: passwordHash,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
});
|
||||||
|
|
||||||
|
return adminUser.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedMonitoringData() {
|
||||||
|
await db.delete(note);
|
||||||
|
await db.delete(domain);
|
||||||
|
await db.delete(service);
|
||||||
|
await db.delete(server);
|
||||||
|
|
||||||
|
await db.insert(server).values(
|
||||||
|
servers.map((item) => ({
|
||||||
|
...item,
|
||||||
|
secondaryIpAddress: item.secondaryIpAddress ?? undefined,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
await db.insert(service).values(
|
||||||
|
services.map((item) => ({
|
||||||
|
...item,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
await db.insert(domain).values(
|
||||||
|
domains.map((item) => ({
|
||||||
|
...item,
|
||||||
|
lastResolvedIp: item.lastResolvedIp ?? undefined,
|
||||||
|
lastCheckMessage: item.lastCheckMessage ?? undefined,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
await db.insert(note).values(
|
||||||
|
notes.map((item) => ({
|
||||||
|
...item,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seed() {
|
||||||
|
await seedAuthAdmin();
|
||||||
|
await seedMonitoringData();
|
||||||
|
}
|
||||||
|
|
||||||
|
seed()
|
||||||
|
.then(() => {
|
||||||
|
console.info("Seeded auth admin and monitoring dashboard sample data.");
|
||||||
|
console.info("Admin login: admin@minmon.local / admin12345");
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Failed to seed dashboard sample data.", error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue