diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 2f1ebc84..6767b86c 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -29,14 +29,14 @@ jobs: npm ci npm run build - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v1 + - name: Setup QEMU + uses: docker/setup-qemu-action@v2 - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 - name: Login to Docker Registry - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} @@ -52,12 +52,13 @@ jobs: fi echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV - - name: Build and Push Docker Image - uses: docker/build-push-action@v2 + - name: Build and Push Multi-Arch Docker Image + uses: docker/build-push-action@v4 with: context: . file: ./docker/Dockerfile push: true + platforms: linux/amd64,linux/arm64 tags: ghcr.io/${{ env.REPO_OWNER }}/termix:${{ env.IMAGE_TAG }} labels: org.opencontainers.image.source=https://github.com/${{ github.repository }} diff --git a/docker/Dockerfile b/docker/Dockerfile index 7b2be3a3..4123d86d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,32 +1,30 @@ -# Build everything in one stage -FROM node:18-alpine +# Use Node.js base image for building frontend +FROM --platform=$BUILDPLATFORM node:18-alpine AS builder -# Install nginx and dependencies -RUN apk add --no-cache nginx npm - -# Set working directory +# Install dependencies +RUN apk add --no-cache npm WORKDIR /app - -# Copy package files and install dependencies COPY package*.json ./ RUN npm install -# Copy the entire project (frontend and backend) +# Copy project files and build frontend COPY ./ ./ - -# Build the frontend RUN npm run build -# Configure nginx -COPY docker/nginx.conf /etc/nginx/nginx.conf +# Final stage with nginx +FROM --platform=$TARGETPLATFORM nginx:alpine -# Copy built frontend to nginx's web directory -COPY ./dist /usr/share/nginx/html +# Copy built frontend from builder stage +COPY --from=builder /app/dist /usr/share/nginx/html + +# Copy nginx config +COPY docker/nginx.conf /etc/nginx/nginx.conf # Expose necessary ports EXPOSE 8080 EXPOSE 8081 -# Use entrypoint.sh to run both the backend and nginx +# Use entrypoint.sh to start backend and nginx +COPY --from=builder /app/src/backend/entrypoint.sh /app/src/backend/entrypoint.sh RUN chmod +x /app/src/backend/entrypoint.sh ENTRYPOINT ["/app/src/backend/entrypoint.sh"] \ No newline at end of file diff --git a/src/App.css b/src/App.css index 779f3eb4..d2f97693 100644 --- a/src/App.css +++ b/src/App.css @@ -44,6 +44,8 @@ border-radius: 4px; padding: 4px; flex-shrink: 0; /* Prevent tabs from shrinking */ + text-align: center; + vertical-align: center; } .tab-item button { @@ -54,13 +56,24 @@ cursor: pointer; } +.tab-item button:hover { + background: #1a1a1a; + border: white 1px solid; +} + .tab-close { padding: 2px 6px !important; - border-radius: 50%; + border-radius: 20%; + text-align: center; + vertical-align: center; + font-weight: bold; + line-height: 1; + cursor: pointer; } .tab-close:hover { - background: #ff5555 !important; + background: #1a1a1a !important; + border: white 1px solid; } .active-tab { diff --git a/src/Terminal.jsx b/src/Terminal.jsx index 463c20b0..864e891b 100644 --- a/src/Terminal.jsx +++ b/src/Terminal.jsx @@ -7,6 +7,7 @@ import PropTypes from "prop-types"; export function NewTerminal({ hostConfig }) { const terminalRef = useRef(null); + const socketRef = useRef(null); useEffect(() => { if (!hostConfig || !terminalRef.current) return; @@ -46,8 +47,8 @@ export function NewTerminal({ hostConfig }) { fitAddon.fit(); const { cols, rows } = terminal; - if (socket) { - socket.emit("resize", { cols, rows }); + if (socketRef.current) { + socketRef.current.emit("resize", { cols, rows }); console.log(`Terminal resized: cols=${cols}, rows=${rows}`); } }; @@ -71,31 +72,36 @@ export function NewTerminal({ hostConfig }) { const isSecure = window.location.protocol === "https:"; let ioUrl = `${isSecure ? "https" : "http"}://${window.location.hostname}:8081/socket.io/`; - if(window.location.hostname === "localhost") { + if (window.location.hostname === "localhost") { ioUrl = "http://localhost:8081"; } const socket = io(ioUrl); + socketRef.current = socket; + + socket.off("connect"); + socket.off("data"); + socket.off("disconnect"); - // Emit hostConfig to start SSH connection socket.on("connect", () => { fitAddon.fit(); resizeTerminal(); // Ensure proper size on connection const { cols, rows } = terminal; socket.emit("connectToHost", cols, rows, hostConfig); terminal.write("\r\n*** Connected to backend ***\r\n"); + }); - terminal.onKey((key) => { - socket.emit("data", key.key); - }); + socket.on("data", (data) => { + terminal.write(data); + }); - socket.on("data", (data) => { - terminal.write(data); - }); + socket.on("disconnect", () => { + terminal.write("\r\n*** Disconnected from backend ***\r\n"); + }); - socket.on("disconnect", () => { - terminal.write("\r\n*** Disconnected from backend ***\r\n"); - }); + // Capture and send keystrokes + terminal.onKey(({ key }) => { + socket.emit("data", key); }); // Cleanup on component unmount diff --git a/src/index.css b/src/index.css index a57d4834..b3a547a2 100644 --- a/src/index.css +++ b/src/index.css @@ -47,7 +47,7 @@ button { transition: border-color 0.25s; } button:hover { - border-color: #646cff; + border-color: #ffffff; } button:focus, button:focus-visible {