blob: b4948546425de91d096a7dd1fdcffd299dd8cd0d [file] [log] [blame]
gioa1efbad2025-05-21 07:16:45 +00001import { z } from "zod";
2
3export const WorkerSchema = z.object({
4 id: z.string(),
5 service: z.string(),
6 address: z.string().url(),
7 status: z.optional(
8 z.object({
gio0afbaee2025-05-22 04:34:33 +00009 commit: z.optional(
10 z.object({
11 hash: z.string(),
12 message: z.string(),
13 }),
14 ),
gioa1efbad2025-05-21 07:16:45 +000015 commands: z.optional(
16 z.array(
17 z.object({
18 command: z.string(),
19 state: z.string(),
20 }),
21 ),
22 ),
23 }),
24 ),
25 logs: z.optional(z.string()),
26});
27
28export type Worker = z.infer<typeof WorkerSchema>;
29
30class ServiceMonitor {
31 private workers: Map<string, string> = new Map();
32 private logs: Map<string, string> = new Map();
33 private statuses: Map<string, Worker["status"]> = new Map();
34
35 constructor(public readonly serviceName: string) {}
36
37 registerWorker(workerId: string, workerAddress: string, workerLog?: string, workerStatus?: Worker["status"]): void {
38 this.workers.set(workerId, workerAddress);
39 if (workerLog) {
40 this.logs.set(workerId, workerLog);
41 }
42 if (workerStatus) {
43 this.statuses.set(workerId, workerStatus);
44 }
45 }
46
47 getWorkerAddress(workerId: string): string | undefined {
48 return this.workers.get(workerId);
49 }
50
51 getWorkerLog(workerId: string): string | undefined {
52 return this.logs.get(workerId);
53 }
54
55 getWorkerStatus(workerId: string): Worker["status"] | undefined {
56 return this.statuses.get(workerId);
57 }
58
59 getAllLogs(): Map<string, string> {
60 return new Map(this.logs);
61 }
62
63 getAllStatuses(): Map<string, Worker["status"]> {
64 return new Map(this.statuses);
65 }
66
67 getWorkerAddresses(): string[] {
68 return Array.from(this.workers.values());
69 }
70
71 getWorkerIds(): string[] {
72 return Array.from(this.workers.keys());
73 }
74
75 hasLogs(): boolean {
76 return this.logs.size > 0;
77 }
gio918780d2025-05-22 08:24:41 +000078
79 async reloadWorker(workerId: string): Promise<void> {
80 const workerAddress = this.workers.get(workerId);
81 if (!workerAddress) {
82 throw new Error(`Worker ${workerId} not found in service ${this.serviceName}`);
83 }
84 try {
85 const response = await fetch(`${workerAddress}/update`, { method: "POST" });
86 if (!response.ok) {
87 throw new Error(
88 `Failed to trigger reload for worker ${workerId} at ${workerAddress}: ${response.statusText}`,
89 );
90 }
91 console.log(`Reload triggered for worker ${workerId} in service ${this.serviceName}`);
92 } catch (error) {
93 console.error(`Error reloading worker ${workerId} in service ${this.serviceName}:`, error);
94 throw error; // Re-throw to be caught by ProjectMonitor
95 }
96 }
gioa1efbad2025-05-21 07:16:45 +000097}
98
99export class ProjectMonitor {
100 private serviceMonitors: Map<string, ServiceMonitor> = new Map();
101
102 constructor() {}
103
104 registerWorker(workerData: Worker): void {
105 let serviceMonitor = this.serviceMonitors.get(workerData.service);
106 if (!serviceMonitor) {
107 serviceMonitor = new ServiceMonitor(workerData.service);
108 this.serviceMonitors.set(workerData.service, serviceMonitor);
109 }
110 serviceMonitor.registerWorker(workerData.id, workerData.address, workerData.logs, workerData.status);
111 }
112
113 getWorkerAddresses(): string[] {
114 let allAddresses: string[] = [];
115 for (const serviceMonitor of this.serviceMonitors.values()) {
116 allAddresses = allAddresses.concat(serviceMonitor.getWorkerAddresses());
117 }
118 return Array.from(new Set(allAddresses));
119 }
120
121 getWorkerLog(serviceName: string, workerId: string): string | undefined {
122 const serviceMonitor = this.serviceMonitors.get(serviceName);
123 if (serviceMonitor) {
124 return serviceMonitor.getWorkerLog(workerId);
125 }
126 return undefined;
127 }
128
129 getAllServiceNames(): string[] {
130 return Array.from(this.serviceMonitors.keys());
131 }
132
133 hasLogs(): boolean {
134 for (const serviceMonitor of this.serviceMonitors.values()) {
135 if (serviceMonitor.hasLogs()) {
136 return true;
137 }
138 }
139 return false;
140 }
141
142 getServiceMonitor(serviceName: string): ServiceMonitor | undefined {
143 return this.serviceMonitors.get(serviceName);
144 }
145
146 getWorkerStatusesForService(serviceName: string): Map<string, Worker["status"]> {
147 const serviceMonitor = this.serviceMonitors.get(serviceName);
148 if (serviceMonitor) {
149 return serviceMonitor.getAllStatuses();
150 }
151 return new Map();
152 }
gio918780d2025-05-22 08:24:41 +0000153
154 async reloadWorker(serviceName: string, workerId: string): Promise<void> {
155 const serviceMonitor = this.serviceMonitors.get(serviceName);
156 if (!serviceMonitor) {
157 throw new Error(`Service ${serviceName} not found`);
158 }
159 await serviceMonitor.reloadWorker(workerId);
160 }
gioa1efbad2025-05-21 07:16:45 +0000161}