Skip to content

Commit

Permalink
Merge pull request #4 from HUAHUAI23/main
Browse files Browse the repository at this point in the history
  • Loading branch information
0fatal authored Jun 5, 2024
2 parents 5f0c7a9 + 348c0eb commit 5086772
Show file tree
Hide file tree
Showing 13 changed files with 512 additions and 127 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ update-changelog.sh
runtimes/nodejs-esm
yarn.lock
deploy/logs/sealos.log
deploy/registry
8 changes: 7 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,5 +149,11 @@
"i18n-ally.keysInUse": [
"description.part2_whatever"
],
"jest.rootPath": "e2e"
"jest.rootPath": "e2e",
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
}
37 changes: 37 additions & 0 deletions server/src/application/pod.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import { PodNameListDto, ContainerNameListDto } from './dto/pod.dto'
import { LABEL_KEY_APP_ID } from 'src/constants'
import { UserWithKubeconfig } from 'src/user/entities/user'

export type PodStatus = {
appid: string
podStatus: {
name: string
podStatus: string
initContainerId?: string
}[]
}

@Injectable()
export class PodService {
private readonly logger = new Logger(PodService.name)
Expand Down Expand Up @@ -48,4 +57,32 @@ export class PodService {

return containerNames
}

async getPodStatusListByAppid(
user: UserWithKubeconfig,
appid: string,
): Promise<PodStatus> {
const coreV1Api = this.cluster.makeCoreV1Api(user)
const res: { response: http.IncomingMessage; body: V1PodList } =
await coreV1Api.listNamespacedPod(
user.namespace,
undefined,
undefined,
undefined,
undefined,
`${LABEL_KEY_APP_ID}=${appid}`,
)
const podStatus: PodStatus = {
appid: appid,
podStatus: [],
}
for (const item of res.body.items) {
podStatus.podStatus.push({
name: item.metadata.name,
podStatus: item.status.phase,
initContainerId: item.status.initContainerStatuses[0]?.containerID,
})
}
return podStatus
}
}
102 changes: 72 additions & 30 deletions server/src/log/log.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import {
Query,
UseGuards,
Sse,
MessageEvent,
} from '@nestjs/common'
import http from 'http'
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'
import { FunctionService } from '../function/function.service'
import { JwtAuthGuard } from 'src/authentication/jwt.auth.guard'
import { ApplicationAuthGuard } from 'src/authentication/application.auth.guard'
import { PassThrough } from 'nodemailer/lib/xoauth2'
import { Log } from '@kubernetes/client-node'
import { RegionService } from 'src/region/region.service'
import { ClusterService } from 'src/region/cluster/cluster.service'
import { Observable } from 'rxjs'
import { PodService } from 'src/application/pod.service'
Expand Down Expand Up @@ -44,23 +43,36 @@ export class LogController {
containerName = appid
}

let podNameList: string[] = (
await this.podService.getPodNameListByAppid(user, appid)
).podNameList
const podStatus = await this.podService.getPodStatusListByAppid(user, appid)

if (!podNameList.includes(podName) && podName !== 'all') {
if (!podStatus.podStatus[0]) {
return new Observable<MessageEvent>((subscriber) => {
subscriber.next(
JSON.stringify({
error: 'podName not exist',
}) as unknown as MessageEvent,
)
subscriber.complete()
subscriber.error(new Error('pod not exist'))
})
}

const podNameList = podStatus.podStatus.map((pod) => pod.name)

const initContainerId = podStatus.podStatus.map(
(pod) => pod.initContainerId,
)

if (containerName === 'init') {
for (const containerId of initContainerId) {
if (!containerId) {
return new Observable<MessageEvent>((subscriber) => {
subscriber.error(new Error('init container not exist'))
})
}
}
}

if (podName !== 'all') {
podNameList = undefined
if (!podNameList.includes(podName)) {
return new Observable<MessageEvent>((subscriber) => {
subscriber.error(new Error('podName not exist'))
})
}
}

const kc = this.clusterService.loadKubeConfig(user)
Expand All @@ -70,14 +82,34 @@ export class LogController {
const logs = new Log(kc)

const streamsEnded = new Set<string>()
const k8sLogResponses: http.IncomingMessage[] = []
const podLogStreams: PassThrough[] = []

const destroyStream = () => {
combinedLogStream?.removeAllListeners()
combinedLogStream?.destroy()
combinedLogStream.removeAllListeners()
combinedLogStream.destroy()

k8sLogResponses.forEach((response) => {
response.removeAllListeners()
response.destroy()
})

podLogStreams.forEach((stream) => {
stream.removeAllListeners()
stream.destroy()
})
}

let idCounter = 1
combinedLogStream.on('data', (chunk) => {
subscriber.next(chunk.toString() as MessageEvent)
const dataString = chunk.toString()
const messageEvent: MessageEvent = {
id: idCounter.toString(),
data: dataString,
type: 'log',
}
idCounter++
subscriber.next(messageEvent)
})

combinedLogStream.on('error', (error) => {
Expand All @@ -86,18 +118,18 @@ export class LogController {
destroyStream()
})

combinedLogStream.on('end', () => {
combinedLogStream.on('close', () => {
subscriber.complete()
destroyStream()
})

const fetchLog = async (podName: string) => {
let k8sResponse: http.IncomingMessage | undefined
const podLogStream = new PassThrough()
streamsEnded.add(podName)
podLogStreams.push(podLogStream)

try {
k8sResponse = await logs.log(
const k8sResponse: http.IncomingMessage = await logs.log(
user.namespace,
podName,
containerName,
Expand All @@ -110,39 +142,49 @@ export class LogController {
tailLines: 1000,
},
)

k8sLogResponses.push(k8sResponse)

podLogStream.pipe(combinedLogStream, { end: false })

podLogStream.on('error', (error) => {
combinedLogStream.emit('error', error)
podLogStream.removeAllListeners()
podLogStream.destroy()
subscriber.error(error)
this.logger.error(`podLogStream error for pod ${podName}`, error)
destroyStream()
})

podLogStream.once('end', () => {
k8sResponse.on('close', () => {
streamsEnded.delete(podName)
if (streamsEnded.size === 0) {
combinedLogStream.end()
combinedLogStream.emit('close')
}
})

podLogStream.on('close', () => {
streamsEnded.delete(podName)
if (streamsEnded.size === 0) {
combinedLogStream.emit('close')
}
})
} catch (error) {
this.logger.error(`Failed to get logs for pod ${podName}`, error)
subscriber.error(error)
k8sResponse?.destroy()
podLogStream.removeAllListeners()
podLogStream.destroy()
this.logger.error(`Failed to get logs for pod ${podName}`, error)
destroyStream()
}
}

if (podNameList && podNameList.length > 0) {
if (podName === 'all' && podNameList.length > 0) {
podNameList.forEach((podName) => {
fetchLog(podName)
})
} else {
fetchLog(podName)
}

// Clean up when the client disconnects
return () => destroyStream()
return () => {
destroyStream()
}
})
}
}
39 changes: 32 additions & 7 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@codingame/monaco-vscode-typescript-basics-default-extension": "~1.82.3",
"@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@microsoft/fetch-event-source": "^2.0.1",
"@monaco-editor/react": "^4.6.0",
"@patternfly/react-log-viewer": "^5.0.0",
"@sentry/integrations": "^7.73.0",
Expand Down
26 changes: 18 additions & 8 deletions web/src/components/Editor/TSEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useRef } from "react";
import { useState } from "react";
import { useCompletionFeature } from "react-monaco-copilot";
import { Spinner } from "@chakra-ui/react";
import { Editor, Monaco } from "@monaco-editor/react";
Expand All @@ -25,6 +26,8 @@ export default function TSEditor(props: {
}) {
const { value, path, fontSize, onChange, colorMode } = props;

const [isEditorMounted, setIsEditorMounted] = useState(false);

const functionCache = useFunctionCache();
const { currentFunction, allFunctionList } = useFunctionStore((state) => state);
const { commonSettings } = useCustomSettingStore();
Expand Down Expand Up @@ -66,23 +69,30 @@ export default function TSEditor(props: {
loadModelsRef.current(monacoRef.current!);
autoImportTypings.loadDefaults(monacoRef.current);
}, 10);

setIsEditorMounted(true);
}

useEffect(() => {
if (monacoRef.current) {
if (isEditorMounted && monacoRef.current) {
loadModelsRef.current(monacoRef.current!);
}
}, [allFunctionList]);
}, [allFunctionList, isEditorMounted]);

useEffect(() => {
const pos = JSON.parse(functionCache.getPositionCache(path) || "{}");
if (pos.lineNumber && pos.column) {
editorRef.current?.setPosition(pos);
editorRef.current?.revealPositionInCenter(pos);
if (isEditorMounted) {
const pos = JSON.parse(functionCache.getPositionCache(path) || "{}");
if (pos.lineNumber && pos.column) {
editorRef.current?.setPosition(pos);
editorRef.current?.revealPositionInCenter(pos);
}

if (monacoRef.current) {
autoImportTypings.parse(value, monacoRef.current);
}
}
autoImportTypings.parse(value, monacoRef.current);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [path]);
}, [path, isEditorMounted]);

const options = {
minimap: {
Expand Down
Loading

0 comments on commit 5086772

Please sign in to comment.