db-seed.ts 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. import { randomUUID } from "crypto";
  2. import "dotenv/config";
  3. import { ConfigService } from "@nestjs/config";
  4. import { DataSource } from "typeorm";
  5. import { buildDataSourceOptions } from "../src/database/database.config";
  6. import { MonitorRunEntity } from "../src/monitoring/monitor-run.entity";
  7. import { MonitorAgentEntity } from "../src/monitoring/monitor-agent.entity";
  8. import { MonitorTargetEntity } from "../src/monitoring/monitor-target.entity";
  9. import { InspectionRunEntity } from "../src/runs/inspection-run.entity";
  10. import { InspectionTaskEntity } from "../src/tasks/inspection-task.entity";
  11. function buildSteps(siteUrl: string, titleKeyword?: string) {
  12. const steps = [
  13. { action: "goto", url: siteUrl, timeoutMs: 20000 },
  14. { action: "waitForLoadState", value: "domcontentloaded", timeoutMs: 12000 },
  15. { action: "waitForSelector", selector: "body", timeoutMs: 12000 },
  16. { action: "assertVisible", selector: "body", timeoutMs: 12000 },
  17. ] as Array<Record<string, unknown>>;
  18. if (titleKeyword) {
  19. steps.push({
  20. action: "assertText",
  21. selector: "title",
  22. contains: titleKeyword,
  23. timeoutMs: 4000,
  24. });
  25. }
  26. steps.push({ action: "screenshot", value: "landing-page" });
  27. return JSON.stringify(steps, null, 2);
  28. }
  29. async function seedInspectionData(dataSource: DataSource) {
  30. const taskRepo = dataSource.getRepository(InspectionTaskEntity);
  31. const runRepo = dataSource.getRepository(InspectionRunEntity);
  32. const tasks = [
  33. {
  34. id: "gov-portal-main-seed",
  35. name: "呼和浩特市政府首页巡检",
  36. siteUrl: "http://www.huhhot.gov.cn/",
  37. cronExpr: "*/15 * * * *",
  38. serviceKey: "gov-portal-main",
  39. titleKeyword: "呼和浩特",
  40. },
  41. {
  42. id: "gov-portal-secondary-seed",
  43. name: "土默特右旗政府首页巡检",
  44. siteUrl: "http://www.tmtyq.gov.cn/",
  45. cronExpr: "*/30 * * * *",
  46. serviceKey: "gov-portal-secondary",
  47. titleKeyword: "土默特右旗",
  48. },
  49. ];
  50. for (const item of tasks) {
  51. let task = await taskRepo.findOne({ where: { id: item.id } });
  52. if (!task) {
  53. task = taskRepo.create({
  54. ...item,
  55. stepsJson: buildSteps(item.siteUrl, item.titleKeyword),
  56. preferredExecutor: "browser",
  57. allowAgentFallback: false,
  58. status: "enabled",
  59. lastRunAt: null,
  60. });
  61. } else {
  62. task.name = item.name;
  63. task.siteUrl = item.siteUrl;
  64. task.cronExpr = item.cronExpr;
  65. task.serviceKey = item.serviceKey;
  66. task.stepsJson = buildSteps(item.siteUrl, item.titleKeyword);
  67. task.preferredExecutor = "browser";
  68. task.allowAgentFallback = false;
  69. task.status = "enabled";
  70. }
  71. await taskRepo.save(task);
  72. const existingRunCount = await runRepo.count({ where: { taskId: task.id } });
  73. if (existingRunCount === 0) {
  74. const startedAt = new Date(Date.now() - 10 * 60 * 1000);
  75. const endedAt = new Date(Date.now() - 9 * 60 * 1000);
  76. const run = runRepo.create({
  77. id: randomUUID(),
  78. taskId: task.id,
  79. traceId: `seed-${task.serviceKey}`,
  80. status: "success",
  81. errorTag: null,
  82. failureReason: null,
  83. screenshotUrls: JSON.stringify([]),
  84. stepLogsJson: JSON.stringify([]),
  85. pageUrl: task.siteUrl,
  86. domSummary: null,
  87. startedAt,
  88. endedAt,
  89. });
  90. await runRepo.save(run);
  91. }
  92. }
  93. }
  94. async function seedMonitoringData(dataSource: DataSource) {
  95. const agentRepo = dataSource.getRepository(MonitorAgentEntity);
  96. const targetRepo = dataSource.getRepository(MonitorTargetEntity);
  97. const runRepo = dataSource.getRepository(MonitorRunEntity);
  98. const agentSeeds = [
  99. {
  100. id: "monitor-agent-local-linux-seed",
  101. name: "本地 Linux Agent 模板",
  102. agentKey: "linux-demo-agent",
  103. host: "192.168.1.50",
  104. platform: "linux" as const,
  105. status: "disabled" as const,
  106. monitoringEnabled: false,
  107. logIngestEnabled: false,
  108. proactivePushEnabled: false,
  109. },
  110. {
  111. id: "monitor-agent-local-windows-seed",
  112. name: "本地 Windows Agent",
  113. agentKey: "local-windows-agent",
  114. host: "localhost",
  115. platform: "windows" as const,
  116. status: "enabled" as const,
  117. monitoringEnabled: true,
  118. logIngestEnabled: false,
  119. proactivePushEnabled: false,
  120. },
  121. ];
  122. for (const item of agentSeeds) {
  123. let agent = await agentRepo.findOne({ where: { id: item.id } });
  124. if (!agent) {
  125. agent = agentRepo.create({
  126. ...item,
  127. version: null,
  128. lastHeartbeatAt: null,
  129. lastHeartbeatMetaJson: null,
  130. lastVerifyAt: null,
  131. lastVerifyStatus: null,
  132. lastVerifyMessage: null,
  133. });
  134. } else {
  135. agent.name = item.name;
  136. agent.agentKey = item.agentKey;
  137. agent.host = item.host;
  138. agent.platform = item.platform;
  139. agent.status = item.status;
  140. agent.monitoringEnabled = item.monitoringEnabled;
  141. agent.logIngestEnabled = item.logIngestEnabled;
  142. agent.proactivePushEnabled = item.proactivePushEnabled;
  143. }
  144. await agentRepo.save(agent);
  145. }
  146. const targets = [
  147. {
  148. id: "monitor-http-seed",
  149. name: "本机 API 健康检查",
  150. type: "http" as const,
  151. serviceKey: "sentai-api",
  152. cronExpr: "*/5 * * * *",
  153. endpoint: "http://127.0.0.1:3001/health",
  154. configJson: JSON.stringify(
  155. {
  156. timeoutMs: 8000,
  157. expectedStatus: 200,
  158. expectedText: "\"ok\":true",
  159. },
  160. null,
  161. 2,
  162. ),
  163. secretJson: JSON.stringify({}, null, 2),
  164. status: "enabled" as const,
  165. },
  166. {
  167. id: "monitor-http-blackbox-template-seed",
  168. name: "HTTP Blackbox 接入模板",
  169. type: "http" as const,
  170. serviceKey: "http-blackbox-template",
  171. cronExpr: "*/10 * * * *",
  172. endpoint: "http://192.168.1.30:8080/health",
  173. configJson: JSON.stringify(
  174. {
  175. collectionMode: "blackbox",
  176. blackboxBaseUrl: "http://192.168.1.50:9115",
  177. blackboxModule: "http_2xx",
  178. blackboxTarget: "http://192.168.1.30:8080/health",
  179. },
  180. null,
  181. 2,
  182. ),
  183. secretJson: JSON.stringify({}, null, 2),
  184. status: "disabled" as const,
  185. },
  186. {
  187. id: "monitor-redis-template-seed",
  188. name: "Redis 接入模板",
  189. type: "redis" as const,
  190. serviceKey: "infra-redis-template",
  191. cronExpr: "*/10 * * * *",
  192. endpoint: "192.168.1.10",
  193. configJson: JSON.stringify(
  194. {
  195. tcpHost: "192.168.1.10",
  196. tcpPort: 6379,
  197. timeoutMs: 5000,
  198. redisSections: ["server", "memory", "stats", "replication"],
  199. },
  200. null,
  201. 2,
  202. ),
  203. secretJson: JSON.stringify(
  204. {
  205. authPassword: "",
  206. sshHost: "",
  207. sshPort: 22,
  208. sshUsername: "",
  209. sshPassword: "",
  210. logFilePath: "",
  211. logTailLines: 120,
  212. logKeyword: "ERROR|Exception|Timeout|Refused",
  213. },
  214. null,
  215. 2,
  216. ),
  217. status: "disabled" as const,
  218. },
  219. {
  220. id: "monitor-java-template-seed",
  221. name: "Java 应用接入模板",
  222. type: "java-app" as const,
  223. serviceKey: "java-app-template",
  224. cronExpr: "*/10 * * * *",
  225. endpoint: "http://192.168.1.20:8080",
  226. configJson: JSON.stringify(
  227. {
  228. javaCollectionMode: "hybrid",
  229. javaHealthPath: "/actuator/health",
  230. javaMetricsPath: "/actuator/metrics",
  231. jmxExporterUrl: "http://192.168.1.20:9404/metrics",
  232. jmxMetricsPath: "",
  233. javaRequireHealthCheck: true,
  234. },
  235. null,
  236. 2,
  237. ),
  238. secretJson: JSON.stringify(
  239. {
  240. sshHost: "",
  241. sshPort: 22,
  242. sshUsername: "",
  243. sshPassword: "",
  244. logFilePath: "/data/logs/app/application.log",
  245. logTailLines: 150,
  246. logKeyword: "ERROR|Exception|Timeout|Refused",
  247. },
  248. null,
  249. 2,
  250. ),
  251. status: "disabled" as const,
  252. },
  253. ];
  254. for (const item of targets) {
  255. let target = await targetRepo.findOne({ where: { id: item.id } });
  256. if (!target) {
  257. target = targetRepo.create({
  258. ...item,
  259. lastRunAt: null,
  260. });
  261. } else {
  262. target.name = item.name;
  263. target.type = item.type;
  264. target.serviceKey = item.serviceKey;
  265. target.cronExpr = item.cronExpr;
  266. target.endpoint = item.endpoint;
  267. target.configJson = item.configJson;
  268. target.secretJson = item.secretJson;
  269. target.status = item.status;
  270. }
  271. await targetRepo.save(target);
  272. }
  273. const healthTarget = await targetRepo.findOneByOrFail({ id: "monitor-http-seed" });
  274. const existingRunCount = await runRepo.count({ where: { targetId: healthTarget.id } });
  275. if (existingRunCount === 0) {
  276. const startedAt = new Date(Date.now() - 5 * 60 * 1000);
  277. const endedAt = new Date(Date.now() - 4 * 60 * 1000);
  278. const run = runRepo.create({
  279. id: randomUUID(),
  280. targetId: healthTarget.id,
  281. traceId: "seed-monitor-http",
  282. status: "success",
  283. errorTag: null,
  284. summary: "API 健康检查通过",
  285. failureReason: null,
  286. metricsJson: JSON.stringify({ statusCode: 200 }, null, 2),
  287. detailsJson: JSON.stringify({ endpoint: healthTarget.endpoint }, null, 2),
  288. logExcerpt: null,
  289. resultJsonUrl: null,
  290. startedAt,
  291. endedAt,
  292. });
  293. await runRepo.save(run);
  294. }
  295. }
  296. async function main() {
  297. const configService = new ConfigService(process.env);
  298. const dataSource = new DataSource(
  299. buildDataSourceOptions(configService, { synchronize: false }),
  300. );
  301. await dataSource.initialize();
  302. await seedInspectionData(dataSource);
  303. await seedMonitoringData(dataSource);
  304. await dataSource.destroy();
  305. }
  306. void main().catch((error) => {
  307. console.error(error);
  308. process.exit(1);
  309. });