Skip to content

Commit d66e183

Browse files
committed
feat:cli color
1 parent 6ad0b06 commit d66e183

File tree

5 files changed

+21
-246
lines changed

5 files changed

+21
-246
lines changed

web-server/app/api/stream/route.ts

Lines changed: 7 additions & 215 deletions
Original file line numberDiff line numberDiff line change
@@ -4,100 +4,16 @@ import { createReadStream, FSWatcher, watch } from 'fs';
44
import { handleRequest, handleSyncServerRequest } from '@/api-helpers/axios';
55
import { ServiceNames } from '@/constants/service';
66
import {
7-
ServiceStatus,
87
UPDATE_INTERVAL,
98
LogFile,
109
LOG_FILES,
1110
StreamEventType,
1211
FileEvent
1312
} from '@/constants/stream';
1413

15-
let watchers: FSWatcher[] = [];
16-
let lastPositions: { [key: string]: number } = {};
17-
18-
const sendFileContent = async (filePath: string, serviceName: string) => {
19-
if (streamClosed) return; // Prevent sending if the stream is closed
20-
console.log(`Sending file content for ${serviceName}`);
21-
22-
return new Promise<void>((resolve, reject) => {
23-
const stream = createReadStream(filePath, {
24-
start: lastPositions[filePath] || 0,
25-
encoding: 'utf8'
26-
});
27-
28-
stream.on('data', (chunk) => {
29-
if (streamClosed) return;
30-
const data = {
31-
type: 'log-update',
32-
serviceName,
33-
content: chunk
34-
};
35-
36-
writer.write(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
37-
lastPositions[filePath] =
38-
(lastPositions[filePath] || 0) + Buffer.byteLength(chunk);
39-
});
40-
41-
stream.on('end', () => resolve());
42-
stream.on('error', (error) => reject(error));
43-
});
44-
};
45-
46-
const startWatchers = () => {
47-
logFiles.forEach(async ({ path, serviceName }) => {
48-
await sendFileContent(path, serviceName);
49-
50-
const watcher = watch(path, async (eventType) => {
51-
if (eventType === 'change') {
52-
console.log(`File ${path} (${serviceName}) has been changed`);
53-
await sendFileContent(path, serviceName);
54-
}
55-
});
56-
57-
watchers.push(watcher);
58-
console.log(`Watcher created for ${path}`);
59-
});
60-
};
61-
62-
const cleanupWatchers = () => {
63-
watchers.forEach((watcher) => watcher.close());
64-
watchers = [];
65-
};
66-
67-
sendStatuses();
68-
startWatchers();
69-
70-
const closeStream = () => {
71-
console.log('Client Disconnected');
72-
if (!streamClosed) {
73-
streamClosed = true;
74-
writer
75-
.close()
76-
.catch((error) => console.error('Error closing writer:', error));
77-
if (timeoutId) {
78-
clearTimeout(timeoutId);
79-
}
80-
}
81-
cleanupWatchers();
82-
};
83-
84-
request.signal.onabort = closeStream;
85-
86-
return new Response(responseStream.readable, {
87-
headers: {
88-
'Content-Type': 'text/event-stream',
89-
Connection: 'keep-alive',
90-
'Cache-Control': 'no-cache, no-transform'
91-
}
92-
});
93-
}
94-
95-
process.setMaxListeners(20);
96-
97-
// Utility function to execute shell commands as promises
9814
const execPromise = (command: string): Promise<string> => {
9915
return new Promise((resolve, reject) => {
100-
exec(command, (error, stdout, stderr) => {
16+
exec(command, (error, stdout) => {
10117
if (error) {
10218
return reject(error);
10319
}
@@ -144,7 +60,7 @@ const checkServiceStatus = async (serviceName: string): Promise<boolean> => {
14460
}
14561
};
14662

147-
=const getStatus = async (): Promise<{
63+
const getStatus = async (): Promise<{
14864
[key in ServiceNames]: { isUp: boolean };
14965
}> => {
15066
const services = Object.values(ServiceNames);
@@ -165,11 +81,7 @@ const checkServiceStatus = async (serviceName: string): Promise<boolean> => {
16581
return statuses;
16682
};
16783

168-
// Stream handling function
169-
export async function GET(request: NextRequest): Promise<Response> {
170-
const responseStream = new TransformStream();
171-
172-
export async function GET(request: NextRequest): Promise<Response> {
84+
export async function GET(): Promise<Response> {
17385
const encoder = new TextEncoder();
17486
let streamClosed = false;
17587
let lastPositions: { [key: string]: number } = {};
@@ -184,6 +96,7 @@ export async function GET(request: NextRequest): Promise<Response> {
18496
const stream = new ReadableStream({
18597
start(controller) {
18698
const pushStatus = async () => {
99+
console.log('push status');
187100
if (streamClosed) return;
188101
try {
189102
const statuses = await getStatus();
@@ -201,6 +114,8 @@ export async function GET(request: NextRequest): Promise<Response> {
201114
};
202115

203116
const pushFileContent = async ({ path, serviceName }: LogFile) => {
117+
console.log('push content');
118+
204119
if (streamClosed) return;
205120
try {
206121
const fileStream = createReadStream(path, {
@@ -240,6 +155,7 @@ export async function GET(request: NextRequest): Promise<Response> {
240155
startWatchers();
241156
},
242157
cancel() {
158+
console.log('CLOSE ');
243159
streamClosed = true;
244160

245161
if (statusTimer) {
@@ -260,127 +176,3 @@ export async function GET(request: NextRequest): Promise<Response> {
260176
}
261177
});
262178
}
263-
264-
const execPromise = (command: string): Promise<string> => {
265-
return new Promise((resolve, reject) => {
266-
exec(command, (error, stdout, _stderr) => {
267-
if (error) {
268-
return reject(error);
269-
}
270-
resolve(stdout);
271-
});
272-
});
273-
};
274-
275-
const checkServiceStatus = async (serviceName: string): Promise<boolean> => {
276-
try {
277-
switch (serviceName) {
278-
case ServiceNames.API_SERVER: {
279-
const response = await handleRequest('');
280-
return response.message.includes('hello world');
281-
}
282-
283-
case ServiceNames.REDIS: {
284-
const REDIS_PORT = process.env.REDIS_PORT;
285-
const response = await execPromise(`redis-cli -p ${REDIS_PORT} ping`);
286-
return response.trim().includes('PONG');
287-
}
288-
289-
case ServiceNames.POSTGRES: {
290-
const POSTGRES_PORT = process.env.DB_PORT;
291-
const POSTGRES_HOST = process.env.DB_HOST;
292-
const response = await execPromise(
293-
`pg_isready -h ${POSTGRES_HOST} -p ${POSTGRES_PORT}`
294-
);
295-
return response.includes('accepting connections');
296-
}
297-
298-
case ServiceNames.SYNC_SERVER: {
299-
const response = await handleSyncServerRequest('');
300-
return response.message.includes('hello world');
301-
}
302-
303-
default:
304-
console.warn(`Service ${serviceName} not recognized.`);
305-
return false;
306-
}
307-
} catch (error) {
308-
console.error(`${serviceName} service is down:`, error);
309-
return false;
310-
}
311-
};
312-
313-
const getStatus = async (): Promise<{
314-
[key in ServiceNames]: { isUp: boolean };
315-
}> => {
316-
const services = Object.values(ServiceNames);
317-
const statuses: { [key in ServiceNames]: { isUp: boolean } } = {
318-
[ServiceNames.API_SERVER]: { isUp: false },
319-
[ServiceNames.REDIS]: { isUp: false },
320-
[ServiceNames.POSTGRES]: { isUp: false },
321-
[ServiceNames.SYNC_SERVER]: { isUp: false }
322-
};
323-
324-
await Promise.all(
325-
services.map(async (service) => {
326-
const isUp = await checkServiceStatus(service);
327-
statuses[service] = { isUp };
328-
})
329-
);
330-
331-
return statuses;
332-
};
333-
334-
// Stream handling function
335-
export async function GET(request: NextRequest): Promise<Response> {
336-
const responseStream = new TransformStream();
337-
const writer = responseStream.writable.getWriter();
338-
const encoder = new TextEncoder();
339-
let timeoutId: NodeJS.Timeout | null = null;
340-
let streamClosed = false;
341-
342-
// Function to send statuses to the client
343-
const sendStatuses = async () => {
344-
if (streamClosed) return; // Prevent sending if the stream is closed
345-
346-
try {
347-
console.log('Fetching service statuses...');
348-
const statuses = await getStatus();
349-
const statusData = { type: 'status-update', statuses };
350-
await writer.write(
351-
encoder.encode(`data: ${JSON.stringify(statusData)}\n\n`)
352-
);
353-
} catch (error) {
354-
console.error('Error sending statuses:', error);
355-
}
356-
357-
// Schedule the next status update
358-
timeoutId = setTimeout(sendStatuses, 15000);
359-
};
360-
361-
// Start the initial status send
362-
sendStatuses();
363-
364-
// Function to close the stream and clear timeout
365-
const closeStream = () => {
366-
console.log('CLIENT DISCONNECTED');
367-
if (!streamClosed) {
368-
streamClosed = true;
369-
writer
370-
.close()
371-
.catch((error) => console.error('Error closing writer:', error));
372-
if (timeoutId) {
373-
clearTimeout(timeoutId);
374-
}
375-
}
376-
};
377-
378-
// Return the response stream
379-
return new Response(responseStream.readable, {
380-
headers: {
381-
'Content-Type': 'text/event-stream',
382-
Connection: 'keep-alive',
383-
'Cache-Control': 'no-cache, no-transform'
384-
}
385-
});
386-
}
File renamed without changes.

web-server/src/components/Service/SystemStatus.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,20 @@ export const SystemStatus: FC = () => {
5656

5757
const { addPage } = useOverlayPage();
5858

59-
const ServiceTitle: Record<ServiceNames, string> = {
59+
const serviceTitle: Record<ServiceNames, string> = {
6060
[ServiceNames.API_SERVER]: 'Backend Server',
6161
[ServiceNames.REDIS]: 'Redis Database',
6262
[ServiceNames.POSTGRES]: 'Postgres Database',
6363
[ServiceNames.SYNC_SERVER]: 'Sync Server'
6464
};
6565

66+
const serviceColor: Record<ServiceNames, string> = {
67+
[ServiceNames.API_SERVER]: '#06d6a0',
68+
[ServiceNames.REDIS]: '#ef476f',
69+
[ServiceNames.POSTGRES]: '#ff70a6',
70+
[ServiceNames.SYNC_SERVER]: '#ab34eb'
71+
};
72+
6673
return (
6774
<FlexBox col gap={2} sx={{ padding: '16px' }}>
6875
<Line bold white fontSize="24px" sx={{ mb: 2 }}>
@@ -85,15 +92,15 @@ export const SystemStatus: FC = () => {
8592
{Object.keys(services).map((serviceName) => {
8693
const serviceKey = serviceName as ServiceNames;
8794
const { isUp } = services[serviceKey];
88-
95+
const borderColor = serviceColor[serviceKey];
8996
return (
9097
<CardRoot
9198
key={serviceName}
9299
onClick={() => {
93100
addPage({
94101
page: {
95102
ui: 'system_logs',
96-
title: `${ServiceTitle[serviceKey]} Logs`,
103+
title: `${serviceTitle[serviceKey]} Logs`,
97104
props: { serviceName }
98105
}
99106
});
@@ -106,9 +113,7 @@ export const SystemStatus: FC = () => {
106113
backgroundColor: 'rgba(255, 255, 255, 0.05)',
107114
borderRadius: '12px',
108115
border: `1px solid ${
109-
isUp
110-
? alpha(theme.colors.success.main, 0.3)
111-
: alpha(theme.colors.error.main, 0.3)
116+
isUp ? borderColor : alpha(theme.colors.error.main, 0.3)
112117
}`,
113118
padding: '16px',
114119
cursor: 'pointer',
@@ -127,7 +132,7 @@ export const SystemStatus: FC = () => {
127132
alignItems: 'center'
128133
}}
129134
>
130-
{ServiceTitle[serviceKey]}
135+
{serviceTitle[serviceKey]}
131136
<Box
132137
component="span"
133138
sx={{

web-server/src/content/Service/SystemLogs.tsx

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,10 @@ import { useEffect, useRef, useMemo } from 'react';
55
import { ServiceNames } from '@/constants/service';
66
import { useSelector } from '@/store';
77

8-
const getColorTheme = (serviceName: ServiceNames): string => {
9-
switch (serviceName) {
10-
case ServiceNames.API_SERVER:
11-
return '#06d6a0';
12-
case ServiceNames.SYNC_SERVER:
13-
return '#ab34eb';
14-
case ServiceNames.REDIS:
15-
return '#ef476f';
16-
case ServiceNames.POSTGRES:
17-
return '#ff70a6';
18-
default:
19-
return '#000000';
20-
}
21-
};
22-
238
export const SystemLogs = ({ serviceName }: { serviceName: ServiceNames }) => {
249
const router = useRouter();
2510
const containerRef = useRef<HTMLDivElement>(null);
26-
2711
const services = useSelector((state) => state.service.services);
28-
2912
const logs = useMemo(() => {
3013
return serviceName ? services[serviceName]?.logs || [] : [];
3114
}, [serviceName, services]);
@@ -58,14 +41,12 @@ export const SystemLogs = ({ serviceName }: { serviceName: ServiceNames }) => {
5841
>
5942
{services &&
6043
logs.map((log, index) => {
61-
const color = getColorTheme(serviceName);
6244
return (
6345
<Typography
6446
key={index}
6547
style={{
6648
marginBottom: '8px',
6749
fontFamily: 'monospace',
68-
border: `0.2px solid ${color}`,
6950
padding: '2px'
7051
}}
7152
>

web-server/src/slices/service.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,8 @@ export const serviceSlice = createSlice({
4444
setStatus: (state, action: PayloadAction<SetStatusPayload>) => {
4545
state.loading = false;
4646
const { statuses } = action.payload;
47-
48-
Object.entries(statuses).forEach(([serviceNameKey, { isUp }]) => {
49-
const serviceName = serviceNameKey as ServiceNames;
50-
const service = state.services[serviceName];
51-
47+
Object.entries(statuses).forEach(([serviceName, { isUp }]) => {
48+
const service = state.services[serviceName as ServiceNames];
5249
if (service) {
5350
service.isUp = isUp;
5451
}

0 commit comments

Comments
 (0)