Compare commits
2 Commits
7a666303be
...
a80744c533
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a80744c533 | ||
|
|
704d0fe0bf |
63
Dockerfile
Normal file
63
Dockerfile
Normal file
@@ -0,0 +1,63 @@
|
||||
# Multi-stage build for Cluster application with Go backend and React frontend
|
||||
|
||||
# Frontend build stage
|
||||
FROM node:18 AS frontend-build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
||||
|
||||
# Install pnpm globally
|
||||
RUN npm install -g pnpm
|
||||
|
||||
# Install frontend dependencies
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Copy frontend source
|
||||
COPY frontend/ .
|
||||
|
||||
# Build frontend
|
||||
RUN pnpm run build
|
||||
|
||||
# Backend build stage
|
||||
FROM golang:1.22 AS backend-build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy go mod files
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
# Download dependencies
|
||||
RUN go mod download
|
||||
|
||||
# Copy only backend source code
|
||||
COPY main.go ./
|
||||
COPY internal/ ./internal/
|
||||
COPY pkg/ ./pkg/
|
||||
|
||||
# Build backend
|
||||
RUN go build -o cluster .
|
||||
|
||||
# Final stage - Nginx server
|
||||
FROM nginx:latest
|
||||
|
||||
# Copy nginx configuration
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
# Copy backend binary
|
||||
COPY --from=backend-build /app/cluster /app/cluster
|
||||
|
||||
# Copy frontend build
|
||||
COPY --from=frontend-build /app/dist /usr/share/nginx/html
|
||||
|
||||
# Create data directory
|
||||
RUN mkdir -p /app/x-storage
|
||||
|
||||
# Expose ports
|
||||
EXPOSE 80
|
||||
|
||||
# Start backend and nginx
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
10
docker-entrypoint.sh
Executable file
10
docker-entrypoint.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Start the Go backend in the background
|
||||
/app/cluster -address 127.0.0.1:9119 -data-dir /data &
|
||||
|
||||
# Wait a moment for backend to start
|
||||
sleep 2
|
||||
|
||||
# Start nginx in the foreground
|
||||
nginx -g 'daemon off;'
|
||||
@@ -76,6 +76,7 @@ export default function K8sResourceList() {
|
||||
const [logsDialogOpen, setLogsDialogOpen] = useState(false)
|
||||
const [logs, setLogs] = useState<string[]>([])
|
||||
const [selectedPod, setSelectedPod] = useState<{ name: string; namespace: string } | null>(null)
|
||||
const eventSourceRef = useRef<EventSource | null>(null)
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
|
||||
const [deleteTarget, setDeleteTarget] = useState<{ name: string; namespace: string } | null>(null)
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
@@ -98,6 +99,17 @@ export default function K8sResourceList() {
|
||||
}
|
||||
}, [selectedKind, namespace, nameFilter])
|
||||
|
||||
// Clean up SSE connection on component unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (eventSourceRef.current) {
|
||||
console.log('Cleaning up SSE connection on component unmount')
|
||||
eventSourceRef.current.close()
|
||||
eventSourceRef.current = null
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const fetchKubeconfig = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/v1/k8s/config')
|
||||
@@ -171,24 +183,62 @@ export default function K8sResourceList() {
|
||||
}
|
||||
|
||||
const handleViewLogs = (podName: string, podNamespace: string) => {
|
||||
console.log('handleViewLogs called with:', { podName, podNamespace })
|
||||
setSelectedPod({ name: podName, namespace: podNamespace })
|
||||
setLogs([])
|
||||
setLogsDialogOpen(true)
|
||||
|
||||
// Close any existing connection
|
||||
if (eventSourceRef.current) {
|
||||
console.log('Closing existing EventSource connection')
|
||||
eventSourceRef.current.close()
|
||||
eventSourceRef.current = null
|
||||
}
|
||||
|
||||
const eventSource = new EventSource(
|
||||
`/api/v1/k8s/pod/logs?name=${encodeURIComponent(podName)}&namespace=${encodeURIComponent(podNamespace)}&tail=1000&follow=true`
|
||||
)
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
// Save reference to the EventSource
|
||||
eventSourceRef.current = eventSource
|
||||
|
||||
// Listen for the specific event type 'pod-logs'
|
||||
eventSource.addEventListener('pod-logs', (event: MessageEvent) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data)
|
||||
if (message.type === 'log') {
|
||||
setLogs((prev) => [...prev, message.data])
|
||||
setTimeout(() => logsEndRef.current?.scrollIntoView({ behavior: 'smooth' }), 100)
|
||||
} else if (message.type === 'EOF') {
|
||||
// Handle end of stream if needed
|
||||
} else if (message.type === 'error') {
|
||||
setLogs((prev) => [...prev, `Error: ${message.data}`])
|
||||
}
|
||||
} catch (e) {
|
||||
// If parsing fails, treat as plain text (fallback)
|
||||
setLogs((prev) => [...prev, event.data])
|
||||
setTimeout(() => logsEndRef.current?.scrollIntoView({ behavior: 'smooth' }), 100)
|
||||
}
|
||||
})
|
||||
|
||||
eventSource.onerror = () => {
|
||||
eventSource.close()
|
||||
console.log('EventSource error occurred')
|
||||
if (eventSourceRef.current) {
|
||||
eventSourceRef.current.close()
|
||||
eventSourceRef.current = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return () => eventSource.close()
|
||||
const handleCloseLogsDialog = () => {
|
||||
console.log('handleCloseLogsDialog called')
|
||||
// Close the EventSource connection if it exists
|
||||
if (eventSourceRef.current) {
|
||||
console.log('Closing EventSource connection')
|
||||
eventSourceRef.current.close()
|
||||
eventSourceRef.current = null
|
||||
}
|
||||
setLogsDialogOpen(false)
|
||||
}
|
||||
|
||||
const handleDeleteResource = async () => {
|
||||
@@ -843,7 +893,7 @@ export default function K8sResourceList() {
|
||||
|
||||
<Dialog
|
||||
open={logsDialogOpen}
|
||||
onClose={() => setLogsDialogOpen(false)}
|
||||
onClose={handleCloseLogsDialog}
|
||||
maxWidth="lg"
|
||||
fullWidth
|
||||
>
|
||||
@@ -852,7 +902,7 @@ export default function K8sResourceList() {
|
||||
<Typography variant="h6">
|
||||
Pod 日志: {selectedPod?.name} ({selectedPod?.namespace})
|
||||
</Typography>
|
||||
<IconButton onClick={() => setLogsDialogOpen(false)}>
|
||||
<IconButton onClick={handleCloseLogsDialog}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
8
go.mod
8
go.mod
@@ -4,7 +4,7 @@ go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/gofiber/fiber/v3 v3.0.0-beta.2
|
||||
github.com/gofiber/fiber/v3 v3.0.0-rc.2
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/google/go-containerregistry v0.20.6
|
||||
github.com/jedib0t/go-pretty/v6 v6.7.1
|
||||
@@ -12,6 +12,7 @@ require (
|
||||
golang.org/x/crypto v0.43.0
|
||||
golang.org/x/net v0.46.0
|
||||
gorm.io/gorm v1.31.1
|
||||
k8s.io/api v0.34.1
|
||||
k8s.io/apimachinery v0.34.1
|
||||
k8s.io/client-go v0.34.1
|
||||
sigs.k8s.io/yaml v1.6.0
|
||||
@@ -32,6 +33,7 @@ require (
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/gofiber/schema v1.6.0 // indirect
|
||||
github.com/gofiber/utils/v2 v2.0.0-rc.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/gnostic-models v0.7.0 // indirect
|
||||
@@ -52,12 +54,13 @@ require (
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/tinylib/msgp v1.4.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.65.0 // indirect
|
||||
github.com/vbatts/tar-split v0.12.1 // indirect
|
||||
@@ -74,7 +77,6 @@ require (
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.34.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
|
||||
|
||||
14
go.sum
14
go.sum
@@ -35,8 +35,10 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gofiber/fiber/v3 v3.0.0-beta.2 h1:mVVgt8PTaHGup3NGl/+7U7nEoZaXJ5OComV4E+HpAao=
|
||||
github.com/gofiber/fiber/v3 v3.0.0-beta.2/go.mod h1:w7sdfTY0okjZ1oVH6rSOGvuACUIt0By1iK0HKUb3uqM=
|
||||
github.com/gofiber/fiber/v3 v3.0.0-rc.2 h1:5I3RQ7XygDBfWRlMhkATjyJKupMmfMAVmnsrgo6wmc0=
|
||||
github.com/gofiber/fiber/v3 v3.0.0-rc.2/go.mod h1:EHKwhVCONMruJTOmvSPSy0CdACJ3uqCY8vGaBXft8yg=
|
||||
github.com/gofiber/schema v1.6.0 h1:rAgVDFwhndtC+hgV7Vu5ItQCn7eC2mBA4Eu1/ZTiEYY=
|
||||
github.com/gofiber/schema v1.6.0/go.mod h1:WNZWpQx8LlPSK7ZaX0OqOh+nQo/eW2OevsXs1VZfs/s=
|
||||
github.com/gofiber/utils/v2 v2.0.0-rc.1 h1:b77K5Rk9+Pjdxz4HlwEBnS7u5nikhx7armQB8xPds4s=
|
||||
github.com/gofiber/utils/v2 v2.0.0-rc.1/go.mod h1:Y1g08g7gvST49bbjHJ1AVqcsmg93912R/tbKWhn6V3E=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
@@ -103,6 +105,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -116,8 +120,8 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shamaton/msgpack/v2 v2.2.3 h1:uDOHmxQySlvlUYfQwdjxyybAOzjlQsD1Vjy+4jmO9NM=
|
||||
github.com/shamaton/msgpack/v2 v2.2.3/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI=
|
||||
github.com/shamaton/msgpack/v2 v2.3.1 h1:R3QNLIGA/tbdczNMZ5PCRxrXvy+fnzsIaHG4kKMgWYo=
|
||||
github.com/shamaton/msgpack/v2 v2.3.1/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
@@ -136,6 +140,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tinylib/msgp v1.4.0 h1:SYOeDRiydzOw9kSiwdYp9UcBgPFtLU2WDHaJXyHruf8=
|
||||
github.com/tinylib/msgp v1.4.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.65.0 h1:j/u3uzFEGFfRxw79iYzJN+TteTJwbYkru9uDp3d0Yf8=
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
@@ -22,7 +23,6 @@ import (
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"sigs.k8s.io/yaml"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func getK8sClient(db *gorm.DB) (*kubernetes.Clientset, error) {
|
||||
@@ -726,21 +726,22 @@ func K8sPodLogs(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handl
|
||||
|
||||
req := clientset.CoreV1().Pods(namespace).GetLogs(podName, podLogOpts)
|
||||
|
||||
logCtx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
logCtx, cancel := context.WithCancel(c.Context())
|
||||
|
||||
stream, err := req.Stream(logCtx)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return resp.R500(c, "", nil, fmt.Errorf("failed to get pod logs: %w", err))
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
// Use the existing SSE manager from resp package
|
||||
manager := resp.SSE(c, "pod-logs")
|
||||
|
||||
// Start streaming logs in a goroutine
|
||||
go func() {
|
||||
defer stream.Close()
|
||||
defer manager.Close()
|
||||
defer cancel()
|
||||
|
||||
reader := bufio.NewReader(stream)
|
||||
for {
|
||||
@@ -751,20 +752,18 @@ func K8sPodLogs(ctx context.Context, db *gorm.DB, store store.Store) fiber.Handl
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
manager.Send("[EOF]")
|
||||
manager.JSON(map[string]any{"type": "EOF"})
|
||||
return
|
||||
}
|
||||
manager.Send(fmt.Sprintf("error: %v", err))
|
||||
manager.JSON(map[string]any{"type": "error", "data": err.Error()})
|
||||
return
|
||||
}
|
||||
manager.Send(line)
|
||||
manager.JSON(map[string]any{"data": line, "type": "log"})
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Return nil since we're handling the response directly
|
||||
c.Context().SetBodyStreamWriter(manager.Writer())
|
||||
return nil
|
||||
return c.SendStreamWriter(manager.Writer())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
51
nginx.conf
Normal file
51
nginx.conf
Normal file
@@ -0,0 +1,51 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
upstream backend {
|
||||
server 127.0.0.1:9119;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
# Serve static files
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Proxy API requests to backend
|
||||
location /api/ {
|
||||
proxy_pass http://backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Proxy OCI registry v2 requests to backend
|
||||
location /v2/ {
|
||||
proxy_pass http://backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Proxy registry requests to backend
|
||||
location /registry/ {
|
||||
proxy_pass http://backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user