Skip to content

Commit 6e5da1a

Browse files
authored
feat: add app startup logs (#1992)
1 parent 829c5eb commit 6e5da1a

File tree

9 files changed

+332
-27
lines changed

9 files changed

+332
-27
lines changed

server/src/application/pod.service.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ import http from 'http'
77
import { PodNameListDto, ContainerNameListDto } from './dto/pod.dto'
88
import { LABEL_KEY_APP_ID } from 'src/constants'
99

10+
export type PodStatus = {
11+
appid: string
12+
podStatus: {
13+
name: string
14+
podStatus: string
15+
initContainerId?: string
16+
}[]
17+
}
1018
@Injectable()
1119
export class PodService {
1220
private readonly logger = new Logger(PodService.name)
@@ -53,4 +61,31 @@ export class PodService {
5361

5462
return containerNames
5563
}
64+
65+
async getPodStatusListByAppid(appid: string): Promise<PodStatus> {
66+
const region = await this.regionService.findByAppId(appid)
67+
const namespaceOfApp = GetApplicationNamespace(region, appid)
68+
const coreV1Api = this.cluster.makeCoreV1Api(region)
69+
const res: { response: http.IncomingMessage; body: V1PodList } =
70+
await coreV1Api.listNamespacedPod(
71+
namespaceOfApp,
72+
undefined,
73+
undefined,
74+
undefined,
75+
undefined,
76+
`${LABEL_KEY_APP_ID}=${appid}`,
77+
)
78+
const podStatus: PodStatus = {
79+
appid: appid,
80+
podStatus: [],
81+
}
82+
for (const item of res.body.items) {
83+
podStatus.podStatus.push({
84+
name: item.metadata.name,
85+
podStatus: item.status.phase,
86+
initContainerId: item.status.initContainerStatuses[0]?.containerID,
87+
})
88+
}
89+
return podStatus
90+
}
5691
}

server/src/log/log.controller.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,18 +109,36 @@ export class LogController {
109109
containerName = appid
110110
}
111111

112-
let podNameList: string[] = (
113-
await this.podService.getPodNameListByAppid(appid)
114-
).podNameList
112+
const podStatus = await this.podService.getPodStatusListByAppid(appid)
115113

116-
if (!podNameList.includes(podName) && podName !== 'all') {
114+
if (!podStatus.podStatus[0]) {
117115
return new Observable<MessageEvent>((subscriber) => {
118-
subscriber.error(new Error('podName not exist'))
116+
subscriber.error(new Error('pod not exist'))
119117
})
120118
}
121119

120+
const podNameList = podStatus.podStatus.map((pod) => pod.name)
121+
122+
const initContainerId = podStatus.podStatus.map(
123+
(pod) => pod.initContainerId,
124+
)
125+
126+
if (containerName === 'init') {
127+
for (const containerId of initContainerId) {
128+
if (!containerId) {
129+
return new Observable<MessageEvent>((subscriber) => {
130+
subscriber.error(new Error('init container not exist'))
131+
})
132+
}
133+
}
134+
}
135+
122136
if (podName !== 'all') {
123-
podNameList = undefined
137+
if (!podNameList.includes(podName)) {
138+
return new Observable<MessageEvent>((subscriber) => {
139+
subscriber.error(new Error('podName not exist'))
140+
})
141+
}
124142
}
125143

126144
const region = await this.regionService.findByAppId(appid)
@@ -223,7 +241,7 @@ export class LogController {
223241
}
224242
}
225243

226-
if (podNameList && podNameList.length > 0) {
244+
if (podName === 'all' && podNameList.length > 0) {
227245
podNameList.forEach((podName) => {
228246
fetchLog(podName)
229247
})

web/src/layouts/Function.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import { Badge, Center, Spinner, useColorMode } from "@chakra-ui/react";
44
import { useQuery } from "@tanstack/react-query";
55
import clsx from "clsx";
66

7-
import { APP_PHASE_STATUS, COLOR_MODE, Pages } from "@/constants/index";
7+
import { APP_PHASE_STATUS, APP_STATUS, COLOR_MODE, Pages } from "@/constants/index";
88

99
import { ApplicationControllerFindOne } from "@/apis/v1/applications";
10+
import InitLog from "@/pages/app/mods/StatusBar/LogsModal/initLog";
1011
import useGlobalStore from "@/pages/globalStore";
1112

1213
export default function FunctionLayout() {
@@ -64,12 +65,17 @@ export default function FunctionLayout() {
6465
</Center>
6566
) : (
6667
<>
67-
{currentApp?.phase !== APP_PHASE_STATUS.Started &&
68-
currentApp?.phase !== APP_PHASE_STATUS.Stopped &&
69-
currentApp?.phase !== APP_PHASE_STATUS.Deleted ? (
68+
{currentApp.phase === APP_PHASE_STATUS.Starting &&
69+
currentApp.state !== APP_STATUS.Restarting ? (
70+
<InitLog />
71+
) : [
72+
APP_PHASE_STATUS.Creating,
73+
APP_PHASE_STATUS.Deleting,
74+
APP_PHASE_STATUS.Stopping,
75+
].includes(currentApp.phase) || currentApp.state === APP_STATUS.Restarting ? (
7076
<div
7177
className={clsx(
72-
"absolute bottom-0 left-0 right-0 top-0 z-[999] flex flex-col items-center justify-center opacity-70 ",
78+
"absolute bottom-0 left-0 right-0 top-0 z-[999] flex flex-col items-center justify-center opacity-70",
7379
darkMode ? "bg-lafDark-100" : "bg-lafWhite-600",
7480
)}
7581
>

web/src/pages/app/functions/mods/HeadPanel/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ function HeadPanel() {
8888
setCurrentFunction(item);
8989
}}
9090
>
91-
<div className="max-w-20 flex truncate">
91+
<div className="flex max-w-20 truncate">
9292
<FunctionDetailPopOver
9393
functionItem={item}
9494
color={selected ? "#00A9A6" : ""}

web/src/pages/app/mods/StatusBar/LogsModal/index.scss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* stylelint-disable selector-class-pattern */
12
#log-viewer-container {
23
.pf-v5-c-text-input-group__icon {
34
visibility: hidden;
@@ -29,3 +30,16 @@
2930
background: #2b7873 !important;
3031
}
3132
}
33+
34+
.log-viewer-container-hide-scrollbar {
35+
&,
36+
& * {
37+
&::-webkit-scrollbar {
38+
width: 0 !important;
39+
height: 0 !important;
40+
}
41+
42+
-ms-overflow-style: none; /* IE and Edge */
43+
scrollbar-width: none; /* Firefox */
44+
}
45+
}

web/src/pages/app/mods/StatusBar/LogsModal/index.tsx

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export default function LogsModal(props: { children: React.ReactElement }) {
4848
const [podName, setPodName] = useState("");
4949
const [containerName, setContainerName] = useState("");
5050
const [isLoading, setIsLoading] = useState(true);
51-
const [rowNumber, setRowNumber] = useState(0);
51+
const [rowCount, setRowCount] = useState(0);
5252
const [paused, setPaused] = useState(false);
5353

5454
const [logs, setLogs] = useState<Log[]>([]);
@@ -58,6 +58,23 @@ export default function LogsModal(props: { children: React.ReactElement }) {
5858

5959
const darkMode = useColorMode().colorMode === "dark";
6060

61+
const addOrUpdateLog = (newLog: Log) => {
62+
setLogs((pre) => {
63+
const existingLogIndex = pre.findIndex((existingLog) => existingLog.id === newLog.id);
64+
65+
if (existingLogIndex !== -1) {
66+
const updatedLogs = [...pre];
67+
updatedLogs[existingLogIndex] = {
68+
...updatedLogs[existingLogIndex],
69+
data: newLog.data,
70+
};
71+
return updatedLogs;
72+
} else {
73+
return [...pre, newLog];
74+
}
75+
});
76+
};
77+
6178
const { data: podData } = useQuery(
6279
["GetPodQuery"],
6380
() => {
@@ -115,9 +132,7 @@ export default function LogsModal(props: { children: React.ReactElement }) {
115132
}
116133

117134
if (msg.event === "log") {
118-
const newLineCount = (msg.data.match(/\n/g) || []).length;
119-
setLogs((pre) => [...pre, msg]);
120-
setRowNumber((prevRowNumber) => prevRowNumber + newLineCount);
135+
addOrUpdateLog(msg);
121136
retryCountRef.current = 0;
122137
}
123138
},
@@ -126,8 +141,7 @@ export default function LogsModal(props: { children: React.ReactElement }) {
126141
// if the server closes the connection unexpectedly, retry:
127142
if (retryCountRef.current < MAX_RETRIES) {
128143
retryCountRef.current += 1;
129-
setRefresh((pre) => !pre);
130-
setPaused(false);
144+
throw new Error("connect closed unexpectedly, retrying...");
131145
}
132146
},
133147

@@ -142,18 +156,23 @@ export default function LogsModal(props: { children: React.ReactElement }) {
142156

143157
useEffect(() => {
144158
if (!isOpen) return;
159+
setRowCount(0);
145160
setLogs([]);
146161
setIsLoading(true);
162+
setPaused(false);
147163
const ctrl = fetchLogs();
164+
148165
return () => {
149166
ctrl?.abort();
150167
};
151168
}, [podName, containerName, isOpen, refresh, fetchLogs]);
152169

153170
useEffect(() => {
154-
const sortedLogs = [...logs].sort((a, b) => parseInt(a.id) - parseInt(b.id));
171+
const sortedLogs = logs.sort((a, b) => parseInt(a.id) - parseInt(b.id));
155172
const concatenatedLogs = sortedLogs.map((log) => log.data).join("");
156173
setRenderLogs(concatenatedLogs);
174+
const totalRows = concatenatedLogs.split("\n").length;
175+
setRowCount(totalRows);
157176
}, [logs]);
158177

159178
useEffect(() => {
@@ -233,21 +252,26 @@ export default function LogsModal(props: { children: React.ReactElement }) {
233252
) : (
234253
<div
235254
id="log-viewer-container"
236-
className="text-sm flex h-full flex-col px-2 font-mono"
255+
className={clsx("text-sm flex h-full flex-col px-2 font-mono", {
256+
"log-viewer-container-hide-scrollbar": !paused,
257+
})}
237258
style={{ fontSize: settingStore.commonSettings.fontSize - 1 }}
238-
onWheel={(e) => {
239-
setPaused(true);
240-
}}
241259
>
242260
<LogViewer
243261
data={renderLogs}
244-
hasLineNumbers={false}
245-
scrollToRow={!paused ? rowNumber + 1 : undefined}
262+
hasLineNumbers={true}
263+
scrollToRow={paused ? undefined : rowCount + 300}
246264
height={"98%"}
247265
onScroll={(e) => {
248266
if (e.scrollOffsetToBottom <= 0) {
249267
setPaused(false);
268+
return;
250269
}
270+
if (!e.scrollUpdateWasRequested) {
271+
setPaused(true);
272+
return;
273+
}
274+
setPaused(false);
251275
}}
252276
toolbar={
253277
<div className="absolute right-24 top-4">
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* stylelint-disable selector-class-pattern */
2+
#log-viewer-cover-container {
3+
.pf-v5-c-text-input-group__icon {
4+
visibility: hidden;
5+
}
6+
7+
.pf-v5-c-text-input-group__text-input:focus {
8+
outline: none !important;
9+
color: #000;
10+
}
11+
12+
.pf-m-current {
13+
background: #91ded9 !important;
14+
}
15+
16+
.pf-m-match {
17+
background: #daf4f2 !important;
18+
}
19+
20+
[data-theme="dark"] & .pf-v5-c-text-input-group__text-input:focus {
21+
outline: none !important;
22+
color: #fff;
23+
}
24+
25+
[data-theme="dark"] & .pf-m-current {
26+
background: #47c8bf !important;
27+
}
28+
29+
[data-theme="dark"] & .pf-m-match {
30+
background: #2b7873 !important;
31+
}
32+
}
33+
34+
.log-viewer-cover-container-hide-scrollbar {
35+
&,
36+
& * {
37+
&::-webkit-scrollbar {
38+
width: 0 !important;
39+
height: 0 !important;
40+
}
41+
42+
-ms-overflow-style: none; /* IE and Edge */
43+
scrollbar-width: none; /* Firefox */
44+
}
45+
}

0 commit comments

Comments
 (0)