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..b1a826c4 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" @@ -79,7 +80,7 @@ jobs: # --- SonarQube self-hosted (nếu bạn set SONAR_HOST_URL) --- - name: SonarQube Scan (self-hosted) if: ${{ env.SONAR_HOST_URL != '' }} - uses: SonarSource/sonarqube-scan-action@v4 + uses: SonarSource/sonarqube-scan-action@v5.3.1 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN_FRONTEND }} SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} 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/config/local-config.json b/config/local-config.json new file mode 100644 index 00000000..77c37244 --- /dev/null +++ b/config/local-config.json @@ -0,0 +1 @@ +{ "IP_SERVER": "http://localhost:8888/api" } diff --git a/config/server-config.json b/config/server-config.json new file mode 100644 index 00000000..a9588ebc --- /dev/null +++ b/config/server-config.json @@ -0,0 +1 @@ +{ "IP_SERVER": "http://72.60.41.133:8888/api" } diff --git a/docker-compose.prod-frontend.yml b/docker-compose.prod-frontend.yml index 9ccfafb9..4df2b179 100644 --- a/docker-compose.prod-frontend.yml +++ b/docker-compose.prod-frontend.yml @@ -5,8 +5,8 @@ services: container_name: codecampus-frontend image: ${DOCKERHUB_USER}/codecampus-frontend:${IMAGE_TAG:-latest} restart: unless-stopped - ports: [ "4200:4200" ] - networks: [ backend ] + ports: ["4200:80"] + networks: [backend] networks: backend: diff --git a/package-lock.json b/package-lock.json index 20fa549b..b9315f3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,6 +63,7 @@ "@types/jasmine": "~5.1.0", "@types/uuid": "^10.0.0", "cypress": "^15.0.0", + "dotenv": "^17.2.2", "jasmine-core": "~5.7.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", @@ -7952,6 +7953,19 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dotenv": { + "version": "17.2.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", + "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/proxy.conf.json b/proxy.conf.json index 40621e54..c6662095 100644 --- a/proxy.conf.json +++ b/proxy.conf.json @@ -1,6 +1,6 @@ { "/api": { - "target": "https://192.168.1.220:8888", + "target": "https://localhost:8888", "secure": false, "changeOrigin": true, "logLevel": "debug" diff --git a/public/assets/config.json b/public/assets/config.json new file mode 100644 index 00000000..47717c1e --- /dev/null +++ b/public/assets/config.json @@ -0,0 +1,3 @@ +{ + "IP_SERVER": "http://localhost:8888/api" +} 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/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..21421eeb 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'; +// // Load runtime config trước khi bootstrap +// fetch('/assets/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));