diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d5b04450..046ad652 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,22 +1,31 @@ version: 2 updates: - # NPM (Angular app) + # (Angular app) - package-ecosystem: "npm" directory: "/" schedule: interval: "daily" time: "02:00" timezone: "Asia/Bangkok" - open-pull-requests-limit: 10 target-branch: "main" - labels: ["dependencies", "npm"] - allow: - - dependency-type: "direct" - - dependency-type: "all" + open-pull-requests-limit: 10 + labels: ["dependencies", "npm", "frontend"] ignore: - # ví dụ giữ cố định major của Angular 19 - dependency-name: "@angular/*" - versions: [">=20"] + update-types: ["version-update:semver-major"] + groups: + angular-core: + patterns: ["@angular/*", "zone.js"] + update-types: ["minor", "patch"] + tooling-and-tests: + patterns: ["typescript","karma*","jasmine*","@types/*","cypress"] + update-types: ["minor", "patch"] + ui-and-md: + patterns: ["highlight.js","marked","github-markdown-css","apexcharts","ng-apexcharts","ngx-*"] + update-types: ["minor", "patch"] + codemirror-suite: + patterns: ["codemirror","@codemirror/*"] + update-types: ["minor", "patch"] # GitHub Actions - package-ecosystem: "github-actions" @@ -24,11 +33,13 @@ updates: schedule: interval: "weekly" day: "monday" - time: "03:00" - timezone: "Asia/Bangkok" + time: "03:00" + timezone: "Asia/Bangkok" + target-branch: "main" labels: ["dependencies", "github-actions"] + open-pull-requests-limit: 10 - # Docker base images (Nginx, Node…) + # Docker images (Nginx, Node…) - package-ecosystem: "docker" directory: "/docker" schedule: @@ -36,4 +47,31 @@ updates: day: "tuesday" time: "04:00" timezone: "Asia/Bangkok" + target-branch: "main" labels: ["dependencies", "docker"] + open-pull-requests-limit: 10 + registries: + - dockerhub + - ghcr + groups: + nginx-node-base: + patterns: ["nginx","node"] + update-types: ["minor", "patch"] + dotnet-base: + patterns: ["mcr.microsoft.com/dotnet/*"] + update-types: ["minor", "patch"] + jre-maven: + patterns: ["eclipse-temurin:*","maven:*"] + update-types: ["minor", "patch"] + +registries: + dockerhub: + type: docker-registry + url: https://index.docker.io/v1/ + username: ${{secrets.DOCKERHUB_USER}} + password: ${{secrets.DOCKERHUB_TOKEN}} + ghcr: + type: docker-registry + url: https://ghcr.io + username: ${{secrets.GHCR_USERNAME}} + password: ${{secrets.GHCR_TOKEN}} diff --git a/.github/workflows/ci-sonar-angular.yml b/.github/workflows/ci-sonar-angular.yml index 6569bc36..449f1eb6 100644 --- a/.github/workflows/ci-sonar-angular.yml +++ b/.github/workflows/ci-sonar-angular.yml @@ -12,6 +12,7 @@ on: - "sonar-project.properties" pull_request: +### permissions: contents: read pull-requests: write @@ -31,7 +32,7 @@ jobs: fetch-depth: 0 # Sonar cần full history để tính blame - name: Set up JDK (for Sonar scanner) - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: "21" diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 00000000..5954298e --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,39 @@ +name: Dependabot Auto-merge + +on: + pull_request_target: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: write + pull-requests: write + +jobs: + automerge: + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-latest + + steps: + - name: Fetch Dependabot metadata + id: meta + uses: dependabot/fetch-metadata@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Auto-approve patch/minor + if: | + steps.meta.outputs.update-type == 'version-update:semver-patch' || + steps.meta.outputs.update-type == 'version-update:semver-minor' + uses: hmarr/auto-approve-action@v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable PR auto-merge (squash) for patch/minor + if: | + steps.meta.outputs.update-type == 'version-update:semver-patch' || + steps.meta.outputs.update-type == 'version-update:semver-minor' + uses: peter-evans/enable-pull-request-automerge@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + pull-request-number: ${{ github.event.pull_request.number }} + merge-method: squash diff --git a/.github/workflows/deploy-frontend.yml b/.github/workflows/deploy-frontend.yml index fe0fcf09..be44abb0 100644 --- a/.github/workflows/deploy-frontend.yml +++ b/.github/workflows/deploy-frontend.yml @@ -106,7 +106,7 @@ jobs: fi } - # docker login (nếu có) + # docker login Docker Hub (nếu có) if [ -n "${DOCKERHUB_USER:-}" ] && [ -n "${DOCKERHUB_TOKEN:-}" ]; then echo "docker login Docker Hub..." echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USER" --password-stdin @@ -114,6 +114,12 @@ jobs: echo "Thiếu DOCKERHUB_USER/DOCKERHUB_TOKEN trong .env (image public thì vẫn OK)." fi + # docker login GHCR (nếu có) + if [ -n "${GHCR_USERNAME:-}" ] && [ -n "${GHCR_TOKEN:-}" ]; then + echo "docker login GHCR..." + echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USERNAME" --password-stdin + fi + echo "Pull image frontend tag ${IMAGE_TAG}…" compose -f docker-compose.prod-frontend.yml --env-file .env pull diff --git a/.github/workflows/frontend-docker-publish.yml b/.github/workflows/frontend-docker-publish.yml index 81019d7c..8b2d2462 100644 --- a/.github/workflows/frontend-docker-publish.yml +++ b/.github/workflows/frontend-docker-publish.yml @@ -20,6 +20,7 @@ permissions: env: DOCKER_REPO: ${{ secrets.DOCKERHUB_USER }}/codecampus-frontend + GHCR_OWNER: ${{ github.repository_owner }} DOCKERFILE_PATH: docker/angular-frontend.Dockerfile PLATFORMS: linux/amd64 @@ -28,32 +29,44 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 - - name: Set up Buildx - uses: docker/setup-buildx-action@v3 - - - name: Derive tags + - name: Derive tags (Docker Hub + GHCR) id: meta + shell: bash run: | - TAGS="" + set -euo pipefail + + # Hạ lowercase owner (an toàn POSIX) + OWNER_LC="$(printf '%s' "${GHCR_OWNER}" | tr '[:upper:]' '[:lower:]')" + echo "OWNER_LC=${OWNER_LC}" >> "$GITHUB_ENV" + if [ "${GITHUB_REF_TYPE}" = "tag" ]; then VERSION="${GITHUB_REF_NAME#v}" - echo "IMAGE_TAG=${VERSION}" >> $GITHUB_ENV - TAGS="${{ env.DOCKER_REPO }}:${VERSION}" + echo "IMAGE_TAG=${VERSION}" >> "$GITHUB_ENV" + HUB="${DOCKER_REPO}:${VERSION}" + GHCR="ghcr.io/${OWNER_LC}/codecampus-frontend:${VERSION}" else SHA_TAG="${GITHUB_SHA::12}" - echo "IMAGE_TAG=${SHA_TAG}" >> $GITHUB_ENV - TAGS="${{ env.DOCKER_REPO }}:${SHA_TAG}" + echo "IMAGE_TAG=${SHA_TAG}" >> "$GITHUB_ENV" + HUB="${DOCKER_REPO}:${SHA_TAG}" + GHCR="ghcr.io/${OWNER_LC}/codecampus-frontend:${SHA_TAG}" if [ "${GITHUB_REF_NAME}" = "main" ]; then - TAGS="${TAGS},${{ env.DOCKER_REPO }}:latest" + HUB="${HUB}"$'\n'"${DOCKER_REPO}:latest" + GHCR="${GHCR}"$'\n'"ghcr.io/${OWNER_LC}/codecampus-frontend:latest" fi fi - echo "tags=${TAGS}" >> $GITHUB_OUTPUT + + # Xuất output "tags" dạng đa dòng + { + echo "tags<<__TAGS__" + printf '%s\n' "$HUB" + printf '%s\n' "$GHCR" + echo "__TAGS__" + } >> "$GITHUB_OUTPUT" - name: Login to Docker Hub uses: docker/login-action@v3 @@ -61,7 +74,14 @@ jobs: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build & Push + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build & Push (Docker Hub + GHCR) uses: docker/build-push-action@v6 with: context: . diff --git a/DockerFile b/DockerFile new file mode 100644 index 00000000..44607438 --- /dev/null +++ b/DockerFile @@ -0,0 +1,13 @@ +# Stage 1: build Angular app +FROM node:20-alpine AS build +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build --prod + +# Stage 2: serve với Nginx +FROM nginx:alpine +COPY --from=build /app/dist/codecampus /usr/share/nginx/html +# copy sẵn default nginx config nếu cần +COPY nginx.conf /etc/nginx/conf.d/default.conf diff --git a/config-local.json b/config-local.json new file mode 100644 index 00000000..28b041be --- /dev/null +++ b/config-local.json @@ -0,0 +1 @@ +{ "apiUrl": "http://localhost:8888/api" } diff --git a/config-server.json b/config-server.json new file mode 100644 index 00000000..45d3b25c --- /dev/null +++ b/config-server.json @@ -0,0 +1 @@ +{ "apiUrl": "http://72.60.41.133:8888/api" } diff --git a/docker-compose.prod-frontend.yml b/docker-compose.prod-frontend.yml index 9ccfafb9..bb262f7e 100644 --- a/docker-compose.prod-frontend.yml +++ b/docker-compose.prod-frontend.yml @@ -5,8 +5,13 @@ services: container_name: codecampus-frontend image: ${DOCKERHUB_USER}/codecampus-frontend:${IMAGE_TAG:-latest} restart: unless-stopped - ports: [ "4200:4200" ] - networks: [ backend ] + ports: ["4200:80"] + volumes: + # Dùng config local + - ./config-local.json:/usr/share/nginx/html/config.json + # Hoặc server + # - ./config-server.json:/usr/share/nginx/html/config.json + networks: [backend] networks: backend: diff --git a/package-lock.json b/package-lock.json index 20fa549b..c86e018c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,14 +10,14 @@ "dependencies": { "@angular-devkit/core": "20.2.1", "@angular-devkit/schematics": "20.2.1", - "@angular/animations": "20.2.3", - "@angular/common": "20.2.3", - "@angular/compiler": "20.2.3", - "@angular/core": "20.2.3", - "@angular/forms": "20.2.3", - "@angular/platform-browser": "20.2.3", - "@angular/platform-browser-dynamic": "20.2.3", - "@angular/router": "20.2.3", + "@angular/animations": "20.3.0", + "@angular/common": "20.3.0", + "@angular/compiler": "20.3.0", + "@angular/core": "20.3.0", + "@angular/forms": "20.3.0", + "@angular/platform-browser": "20.3.0", + "@angular/platform-browser-dynamic": "20.3.0", + "@angular/router": "20.3.0", "@codemirror/lang-cpp": "^6.0.2", "@codemirror/lang-java": "^6.0.1", "@codemirror/lang-javascript": "^6.2.4", @@ -57,9 +57,9 @@ "zone.js": "~0.15.0" }, "devDependencies": { - "@angular/build": "^20.2.1", - "@angular/cli": "20.2.1", - "@angular/compiler-cli": "20.2.3", + "@angular/build": "^20.3.2", + "@angular/cli": "20.3.2", + "@angular/compiler-cli": "20.3.0", "@types/jasmine": "~5.1.0", "@types/uuid": "^10.0.0", "cypress": "^15.0.0", @@ -296,13 +296,13 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.2002.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2002.1.tgz", - "integrity": "sha512-8jotVFz+83avTdeRoLe7wn/F+nnbjywuVHqZ/shDGRHssOtR8fkSCjSsKwPZejU6wsgTxAKFylWRIxydZE8Hzw==", + "version": "0.2003.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.2.tgz", + "integrity": "sha512-3QFQlSg92lz+Zid1CGcnYVuPo0RIyq+TEbaJUQmi7K9Ms0VxVNMIwTNIN3SI6QThD0Bg3sVRtsHWw84qoMwjKA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.2.1", + "@angular-devkit/core": "20.3.2", "rxjs": "7.8.2" }, "engines": { @@ -311,6 +311,34 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/architect/node_modules/@angular-devkit/core": { + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.2.tgz", + "integrity": "sha512-MsYPu/WaHQInCxLRfX3vOaf4uedvwX5yI29X/tQpD59/gI5Yq4YMDT48ntryZHclRuQ9x4vdm2Gp9e/LcP0ydw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.3", + "rxjs": "7.8.2", + "source-map": "0.7.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/core": { "version": "20.2.1", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.2.1.tgz", @@ -357,9 +385,9 @@ } }, "node_modules/@angular/animations": { - "version": "20.2.3", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.2.3.tgz", - "integrity": "sha512-cyON3oVfaotz8d8DHP3uheC/XDG2gJD8aiyuG/SEAZ2X1S/tAHdVetESbDZM830lLdi+kB/3GBrMbWCCpMWD7Q==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.0.tgz", + "integrity": "sha512-rCojVsJHaReDfSB4lwcWYJAfbkFXQmcdivdN5m1NavuSlKpWoLw4fLkxkcuOXDjUEwNSb45hRI4ixcwrcuQtmw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -368,19 +396,18 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.2.3", - "@angular/core": "20.2.3" + "@angular/core": "20.3.0" } }, "node_modules/@angular/build": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.2.1.tgz", - "integrity": "sha512-FLiNDUhqCkU7EyODwPl8EZMubWdQG62ynczeLcHGtHOA2/Wiv+CvCP58GbuznZSslEcyyyE7MsEy3ZvsjxZuIA==", + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.2.tgz", + "integrity": "sha512-PiyvOvNtNM9p2YdCgNHTCqF7Fnyq5ug45Zhv7m6yRgin5VcDuSF/Mv2x7AY3HiN+w89Oxozvm13cSanzrw7acA==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2002.1", + "@angular-devkit/architect": "0.2003.2", "@babel/core": "7.28.3", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", @@ -403,7 +430,7 @@ "semver": "7.7.2", "source-map-support": "0.5.21", "tinyglobby": "0.2.14", - "vite": "7.1.2", + "vite": "7.1.5", "watchpack": "2.4.4" }, "engines": { @@ -422,7 +449,7 @@ "@angular/platform-browser": "^20.0.0", "@angular/platform-server": "^20.0.0", "@angular/service-worker": "^20.0.0", - "@angular/ssr": "^20.2.1", + "@angular/ssr": "^20.3.2", "karma": "^6.4.0", "less": "^4.2.0", "ng-packagr": "^20.0.0", @@ -485,19 +512,19 @@ } }, "node_modules/@angular/cli": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.2.1.tgz", - "integrity": "sha512-uKuq4+7EcEer7ixe6cYAAe8/WOvDIbLd/F7ZCMCb5dCGkGRoQKgodo6sorwZUpGvyuXO+mCYarTXzrBrY2b/Cg==", + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.2.tgz", + "integrity": "sha512-5R+f11IbGkNGXTwTfVbhQXCh/jdQxlmdK11P3yoqhj2OcBM+GY8ALCyf0vyoOxocxt7NAJUq4fIpT9W2YhTVLQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.2002.1", - "@angular-devkit/core": "20.2.1", - "@angular-devkit/schematics": "20.2.1", + "@angular-devkit/architect": "0.2003.2", + "@angular-devkit/core": "20.3.2", + "@angular-devkit/schematics": "20.3.2", "@inquirer/prompts": "7.8.2", "@listr2/prompt-adapter-inquirer": "3.0.1", "@modelcontextprotocol/sdk": "1.17.3", - "@schematics/angular": "20.2.1", + "@schematics/angular": "20.3.2", "@yarnpkg/lockfile": "1.1.0", "algoliasearch": "5.35.0", "ini": "5.0.0", @@ -519,15 +546,62 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular/cli/node_modules/@angular-devkit/core": { + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.2.tgz", + "integrity": "sha512-MsYPu/WaHQInCxLRfX3vOaf4uedvwX5yI29X/tQpD59/gI5Yq4YMDT48ntryZHclRuQ9x4vdm2Gp9e/LcP0ydw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.3", + "rxjs": "7.8.2", + "source-map": "0.7.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular/cli/node_modules/@angular-devkit/schematics": { + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.2.tgz", + "integrity": "sha512-CHHq2qWgHNi3fkhBMpSxVSrST2mBN31QfZpvKFp1sWvtJDN7sRHlvLCML81+KplVd8aWkbQqeAG73dgRDPbSBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "20.3.2", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.17", + "ora": "8.2.0", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, "node_modules/@angular/cli/node_modules/@schematics/angular": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.2.1.tgz", - "integrity": "sha512-7Vx11KWooiqxP206JEVgz3cp0rRv31PYnocNoPM6UqLhGtlvL9GdgaZHzDhGFEm0hv6DUFrbTGIzB89gXc54Xg==", + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.2.tgz", + "integrity": "sha512-bd23C6Map7Rfrryc8pZuyPPG8yQLCH863ISo32ARVwiAmBFgjfyNwqC5FsuqHWrYlTzZDzZUk5CjKp1SXxqqxg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.2.1", - "@angular-devkit/schematics": "20.2.1", + "@angular-devkit/core": "20.3.2", + "@angular-devkit/schematics": "20.3.2", "jsonc-parser": "3.3.1" }, "engines": { @@ -550,9 +624,9 @@ } }, "node_modules/@angular/common": { - "version": "20.2.3", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.2.3.tgz", - "integrity": "sha512-QLffWL8asy2oG7p3jvoNmx9s1V1WuJAm6JmQ1S8J3AN/BxumCJan49Nj8rctP8J4uwJDPQV48hqbXUdl1v7CDg==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.0.tgz", + "integrity": "sha512-Il0HqdRdrmI8ufLXd49EYaa/BPqfiSqe5uuKrDxhkAdbRXwCXWsxbO/n8AwilwWn3CKLOCrEXQYKwbcFW0nYQQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -561,14 +635,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "20.2.3", + "@angular/core": "20.3.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "20.2.3", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.2.3.tgz", - "integrity": "sha512-vYGDluko8zAIWhQmKijhcGO0tzanwGONDRgbJ01mCqUsQV+XwmDgUUDZKrUY9uve0wxxM3Xvo4/BjEpGpeG75w==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.0.tgz", + "integrity": "sha512-DvGDusjsDhxIX+nDzihSCGo81Fa8y94KB/bh24eyPwJWV6b0OkawFSvVwzxx8prV0UnNkCN1S/UoZXmtVZGJ4A==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -578,9 +652,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "20.2.3", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.2.3.tgz", - "integrity": "sha512-adLyPXmKbH8VZJCyOraaha+RPTdAjEBRTqwZ5YkjkViTMMANFkuj1w3pDwQsG3LFknRJ99aym+9neGINeAaI7A==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.0.tgz", + "integrity": "sha512-umnZzzKw9RqDVkotYIyupJiKXQpU8knehMUBT1G3QwdeHppC+d/opxISYTkQtY/4IUAsZFLMukWIr82as0DSmw==", "dev": true, "license": "MIT", "dependencies": { @@ -601,7 +675,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.2.3", + "@angular/compiler": "20.3.0", "typescript": ">=5.8 <6.0" }, "peerDependenciesMeta": { @@ -611,9 +685,9 @@ } }, "node_modules/@angular/core": { - "version": "20.2.3", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.2.3.tgz", - "integrity": "sha512-pFMfg11X8SNNZHcLa+wy4y+eAN3FApt+wPzaxkaXaJ64c+tyHcrPNLotoWgE0jmiw8Idn4gGjKAL/WC0uw5dQA==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.0.tgz", + "integrity": "sha512-4uH2TAMm1nXqQ9lcZyyNkjcdQ0Fjcf9Hh0HYrhMOEV6GAUHvM2I8Vr2dSQ40p/UKLEfe9+cpZ78EPocqPQCG6A==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -622,7 +696,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.2.3", + "@angular/compiler": "20.3.0", "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0" }, @@ -636,9 +710,9 @@ } }, "node_modules/@angular/forms": { - "version": "20.2.3", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.2.3.tgz", - "integrity": "sha512-efMn/Hnspg91SzRTm69WpyGq0dgbCtWqUOrR0iZXTR/oDlJw9F/y/nrST36tOBwRNT0QQ2iU5z43iJY1Rl1Bng==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.0.tgz", + "integrity": "sha512-/KGCZUskk8imxz2e47CKe5Ykh3eqEDop0b9YUkZTvJ/dY/cdFK89RAK2xUvOlyUr2mkcByzdzyOhHaM9XEaELg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -647,16 +721,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.2.3", - "@angular/core": "20.2.3", - "@angular/platform-browser": "20.2.3", + "@angular/common": "20.3.0", + "@angular/core": "20.3.0", + "@angular/platform-browser": "20.3.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/platform-browser": { - "version": "20.2.3", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.2.3.tgz", - "integrity": "sha512-oNaRqcGUve+E/CwR9fJb8uern5rb7qNOis1bZRdPXq5rHKaWgDCxUPkoqxRi0EytorntuYsWYPUPW3ul4Ea9tw==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.0.tgz", + "integrity": "sha512-/KsgfxDwP7/KXGrLLSyg4+Xd8HxmHi5dVCu+xHfa3QjzVIvvZfWZLxQj7guRlDtg/mz+t0/OSKvSUZzOAfVzGQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -665,9 +739,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/animations": "20.2.3", - "@angular/common": "20.2.3", - "@angular/core": "20.2.3" + "@angular/animations": "20.3.0", + "@angular/common": "20.3.0", + "@angular/core": "20.3.0" }, "peerDependenciesMeta": { "@angular/animations": { @@ -676,9 +750,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "20.2.3", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.2.3.tgz", - "integrity": "sha512-uxqLv2yNibd2vf3OObyH4arVfwu+o9FKgkUcnFUdowK31emiZe1nAY8uEe/92JIsMMAoIllI/GAVzWH8dp05mg==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.0.tgz", + "integrity": "sha512-8zu4naXyP926+UKTadMM7163sl3JaVY9SVL0qegK5TiB1s0l6vVQ125nzT1BI9HadvCLdtl5ZNZF4P87h7nfwg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -687,16 +761,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.2.3", - "@angular/compiler": "20.2.3", - "@angular/core": "20.2.3", - "@angular/platform-browser": "20.2.3" + "@angular/common": "20.3.0", + "@angular/compiler": "20.3.0", + "@angular/core": "20.3.0", + "@angular/platform-browser": "20.3.0" } }, "node_modules/@angular/router": { - "version": "20.2.3", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.2.3.tgz", - "integrity": "sha512-r8yGJcxHPfeIHZOoyCxN2H4nMgBD/k4TVTFaq8MHf5ryy1iLzayIMPJTFaZe7xpwlJJuBYEjBrYfUN38fYKWgA==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.0.tgz", + "integrity": "sha512-JshumajvPCMztz1+7r/l5tRxFL3cn2jCpr5szdc5hESkpytY4050hedd09GogL1UoIyZAjhyYLhSlMnvrgjHBA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -705,9 +779,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.2.3", - "@angular/core": "20.2.3", - "@angular/platform-browser": "20.2.3", + "@angular/common": "20.3.0", + "@angular/core": "20.3.0", + "@angular/platform-browser": "20.3.0", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -14382,18 +14456,18 @@ "license": "MIT" }, "node_modules/vite": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", - "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", + "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.6", + "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", - "tinyglobby": "^0.2.14" + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" @@ -14456,6 +14530,23 @@ } } }, + "node_modules/vite/node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", diff --git a/package.json b/package.json index ba7be9a4..2bc33532 100644 --- a/package.json +++ b/package.json @@ -12,14 +12,14 @@ "dependencies": { "@angular-devkit/core": "20.2.1", "@angular-devkit/schematics": "20.2.1", - "@angular/animations": "20.2.3", - "@angular/common": "20.2.3", - "@angular/compiler": "20.2.3", - "@angular/core": "20.2.3", - "@angular/forms": "20.2.3", - "@angular/platform-browser": "20.2.3", - "@angular/platform-browser-dynamic": "20.2.3", - "@angular/router": "20.2.3", + "@angular/animations": "20.3.0", + "@angular/common": "20.3.0", + "@angular/compiler": "20.3.0", + "@angular/core": "20.3.0", + "@angular/forms": "20.3.0", + "@angular/platform-browser": "20.3.0", + "@angular/platform-browser-dynamic": "20.3.0", + "@angular/router": "20.3.0", "@codemirror/lang-cpp": "^6.0.2", "@codemirror/lang-java": "^6.0.1", "@codemirror/lang-javascript": "^6.2.4", @@ -59,9 +59,9 @@ "zone.js": "~0.15.0" }, "devDependencies": { - "@angular/build": "^20.2.1", - "@angular/cli": "20.2.1", - "@angular/compiler-cli": "20.2.3", + "@angular/build": "^20.3.2", + "@angular/cli": "20.3.2", + "@angular/compiler-cli": "20.3.0", "@types/jasmine": "~5.1.0", "@types/uuid": "^10.0.0", "cypress": "^15.0.0", diff --git a/public/config.json b/public/config.json new file mode 100644 index 00000000..28b041be --- /dev/null +++ b/public/config.json @@ -0,0 +1 @@ +{ "apiUrl": "http://localhost:8888/api" } diff --git a/src/app/core/services/config-service/api.enpoints.ts b/src/app/core/services/config-service/api.enpoints.ts index 383e3d3d..b2edc1bd 100644 --- a/src/app/core/services/config-service/api.enpoints.ts +++ b/src/app/core/services/config-service/api.enpoints.ts @@ -12,7 +12,8 @@ export const version = '/v1'; export const API_CONFIG = { BASE_URLS: { - MAIN_API: environment.IP_SERVER + version, + MAIN_API: + ((window as any).env?.API_URL || 'http://localhost:8888/api') + version, SECONDARY_API: '', }, ENDPOINTS: { diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 6a7f40fd..89f9dd68 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,7 +1,4 @@ export const environment = { production: true, IP_SERVER: 'http://72.60.41.133:8888/api', - IP_SERVER_NO_SSL: 'http://192.168.1.220:8000/api', - IP_SERVER_RADMIN: 'http://26.100.147.137:8888/api', - IP_LOCAL: 'http://localhost:8081/api', }; diff --git a/src/main.ts b/src/main.ts index 190f3418..dcd81db2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,4 +2,13 @@ import { bootstrapApplication } from '@angular/platform-browser'; import { appConfig } from './app/app.config'; import { App } from './app/app'; -bootstrapApplication(App, appConfig).catch((err) => console.error(err)); +// Load runtime config trước khi bootstrap +fetch('/config.json') + .then((response) => response.json()) + .then((config) => { + (window as any).env = config; + return bootstrapApplication(App, appConfig); + }) + .catch((err) => console.error(err)); + +// bootstrapApplication(App, appConfig).catch((err) => console.error(err));