Commit 067c6f47 authored by Dimitrios Gogos's avatar Dimitrios Gogos
Browse files

feat: update Dockerfile and add remove deployment button for existing apps

parent 2d605175
Loading
Loading
Loading
Loading
+15 −7
Original line number Diff line number Diff line
FROM node:20-slim

# Stage 1 – install dependencies
FROM node:20-alpine AS deps
WORKDIR /app

COPY package.json package-lock.json* ./
RUN npm install --legacy-peer-deps

FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

RUN npm run build
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

EXPOSE 3000

CMD ["npm", "run", "start"]
CMD ["node", "server.js"]
+1 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  reactCompiler: true,
  output: "standalone",
};

export default nextConfig;
+24 −18
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@
      "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
      "dev": true,
      "license": "MIT",
      "peer": true,
      "dependencies": {
        "@babel/code-frame": "^7.29.0",
        "@babel/generator": "^7.29.0",
@@ -164,7 +165,7 @@
      "version": "7.27.1",
      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
      "dev": true,
      "devOptional": true,
      "license": "MIT",
      "engines": {
        "node": ">=6.9.0"
@@ -174,7 +175,7 @@
      "version": "7.28.5",
      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
      "dev": true,
      "devOptional": true,
      "license": "MIT",
      "engines": {
        "node": ">=6.9.0"
@@ -279,7 +280,7 @@
      "version": "7.29.0",
      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
      "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
      "dev": true,
      "devOptional": true,
      "license": "MIT",
      "dependencies": {
        "@babel/helper-string-parser": "^7.27.1",
@@ -2178,18 +2179,6 @@
        "node": "^18 || ^20 || >= 21"
      }
    },
    "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/tree-sitter": {
      "version": "0.22.4",
      "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.22.4.tgz",
      "integrity": "sha512-usbHZP9/oxNsUY65MQUsduGRqDHQOou1cagUSwjhoSYAmSahjQDAVsh9s+SlZkn8X8+O1FULRGwHu7AFP3kjzg==",
      "hasInstallScript": true,
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "node-addon-api": "^8.3.0",
        "node-gyp-build": "^4.8.4"
      }
    },
    "node_modules/@swagger-api/apidom-reference": {
      "version": "1.6.0",
      "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.6.0.tgz",
@@ -2379,6 +2368,7 @@
      "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
      "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
      "license": "MIT",
      "peer": true,
      "dependencies": {
        "csstype": "^3.2.2"
      }
@@ -2466,6 +2456,7 @@
      "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
      "dev": true,
      "license": "MIT",
      "peer": true,
      "dependencies": {
        "@typescript-eslint/scope-manager": "8.56.1",
        "@typescript-eslint/types": "8.56.1",
@@ -2991,6 +2982,7 @@
      "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
      "dev": true,
      "license": "MIT",
      "peer": true,
      "bin": {
        "acorn": "bin/acorn"
      },
@@ -3305,8 +3297,9 @@
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz",
      "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==",
      "dev": true,
      "devOptional": true,
      "license": "MIT",
      "peer": true,
      "dependencies": {
        "@babel/types": "^7.26.0"
      }
@@ -3395,6 +3388,7 @@
        }
      ],
      "license": "MIT",
      "peer": true,
      "dependencies": {
        "baseline-browser-mapping": "^2.9.0",
        "caniuse-lite": "^1.0.30001759",
@@ -4138,6 +4132,7 @@
      "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
      "dev": true,
      "license": "MIT",
      "peer": true,
      "dependencies": {
        "@eslint-community/eslint-utils": "^4.8.0",
        "@eslint-community/regexpp": "^4.12.1",
@@ -4323,6 +4318,7 @@
      "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
      "dev": true,
      "license": "MIT",
      "peer": true,
      "dependencies": {
        "@rtsao/scc": "^1.1.0",
        "array-includes": "^3.1.9",
@@ -5134,7 +5130,8 @@
      "version": "5.1.4",
      "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
      "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
      "license": "MIT"
      "license": "MIT",
      "peer": true
    },
    "node_modules/import-fresh": {
      "version": "3.3.1",
@@ -6599,6 +6596,7 @@
      "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz",
      "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==",
      "license": "MIT",
      "peer": true,
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/ramda"
@@ -6647,6 +6645,7 @@
      "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
      "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
      "license": "MIT",
      "peer": true,
      "engines": {
        "node": ">=0.10.0"
      }
@@ -6656,6 +6655,7 @@
      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
      "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
      "license": "MIT",
      "peer": true,
      "dependencies": {
        "scheduler": "^0.27.0"
      },
@@ -6795,7 +6795,8 @@
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
      "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
      "license": "MIT"
      "license": "MIT",
      "peer": true
    },
    "node_modules/reflect.getprototypeof": {
      "version": "1.0.10",
@@ -7111,6 +7112,7 @@
      "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz",
      "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==",
      "license": "MIT",
      "peer": true,
      "dependencies": {
        "chokidar": "^4.0.0",
        "immutable": "^5.0.2",
@@ -7719,6 +7721,7 @@
      "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
      "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==",
      "license": "MIT",
      "peer": true,
      "engines": {
        "node": ">=0.10.0"
      }
@@ -7808,6 +7811,7 @@
      "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
      "dev": true,
      "license": "MIT",
      "peer": true,
      "engines": {
        "node": ">=12"
      },
@@ -8074,6 +8078,7 @@
      "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
      "dev": true,
      "license": "Apache-2.0",
      "peer": true,
      "bin": {
        "tsc": "bin/tsc",
        "tsserver": "bin/tsserver"
@@ -8455,6 +8460,7 @@
      "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
      "dev": true,
      "license": "MIT",
      "peer": true,
      "funding": {
        "url": "https://github.com/sponsors/colinhacks"
      }
+53 −3
Original line number Diff line number Diff line
"use client";
import React from "react";
import React, { useState } from "react";
import styles from "./myApplications.module.scss";
import { IAppInstance } from "../../utils/interfaces";
import { StatusChip } from "../../components/Chip/StatusChip";
import { ConfirmModal } from "../../components/ConfirmModal/ConfirmModal";

type Props = {
  instance: IAppInstance;
  onDelete: (appInstanceId: string) => void;
};

export const DeploymentCard = ({ instance, onDelete }: Props) => {
  const [deleteOpen, setDeleteOpen] = useState(false);
  const [deleting, setDeleting] = useState(false);
  const [deleteError, setDeleteError] = useState<string | null>(null);

  const handleDelete = async () => {
    setDeleting(true);
    setDeleteError(null);
    try {
      const res = await fetch(`/api/appinstances/${instance.appInstanceId}`, { method: "DELETE" });
      if (res.ok) {
        onDelete(instance.appInstanceId);
        setDeleteOpen(false);
      } else {
        setDeleteError("Failed to remove deployment.");
      }
    } catch {
      setDeleteError("Could not reach the server.");
    } finally {
      setDeleting(false);
    }
  };

export const DeploymentCard = ({ instance }: { instance: IAppInstance }) => {
  return (
    <>
    <div className={styles.deployCard}>
      <div className={styles.header}>
        <h3 className={styles.title}>{instance.name}</h3>
        <div className={styles.headerRight}>
          <StatusChip status={instance.status} />
          <button
            className={styles.deleteBtn}
            onClick={() => setDeleteOpen(true)}
            aria-label="Remove deployment"
          >
            Remove
          </button>
        </div>
      </div>

      <dl className={styles.fields}>
@@ -39,5 +77,17 @@ export const DeploymentCard = ({ instance }: { instance: IAppInstance }) => {
        )}
      </dl>
    </div>

    <ConfirmModal
      open={deleteOpen}
      title="Remove deployment"
      message={`Are you sure you want to remove "${instance.name}"?`}
      confirmLabel="Remove"
      loading={deleting}
      error={deleteError}
      onConfirm={handleDelete}
      onClose={() => { setDeleteOpen(false); setDeleteError(null); }}
    />
    </>
  );
};
+31 −0
Original line number Diff line number Diff line
@@ -266,6 +266,37 @@
    gap: 0.5rem;
  }

  .headerRight {
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }

  .deleteBtn {
    background: none;
    border: 1px solid #e0e0e0;
    border-radius: 4px;
    color: #aaa;
    font-size: 0.72rem;
    font-weight: 600;
    padding: 3px 10px;
    cursor: pointer;
    white-space: nowrap;
    line-height: 1.4;
    transition: all 0.15s ease;

    &:hover:not(:disabled) {
      background: #ffe5e5;
      border-color: #e03a3a;
      color: #e03a3a;
    }

    &:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
  }

  .title {
    font-size: 1.15rem;
    font-weight: 600;
Loading