diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..78afd7c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,51 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build outputs +build/ +dist/ +.svelte-kit/ + +# Development files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Git +.git/ +.gitignore + +# Docker +Dockerfile +docker-compose.yml +.dockerignore + +# Documentation +docs/ +README.md + +# Logs +logs +*.log + +# Coverage +coverage/ + +# Temporary files +*.tmp +*.temp \ No newline at end of file diff --git a/.github/changelog-configuration.json b/.github/changelog-configuration.json index e070584..8d8c392 100644 --- a/.github/changelog-configuration.json +++ b/.github/changelog-configuration.json @@ -2,21 +2,106 @@ "categories": [ { "title": "## ๐Ÿš€ Features", - "labels": ["enhancement"] + "labels": ["enhancement", "feature"], + "rules": [ + { + "pattern": "^feat(\\(.+\\))?:", + "on_property": "title" + } + ] }, { - "title": "## ๐Ÿ› Fixes", - "labels": ["bug"] + "title": "## ๐Ÿ› Bug Fixes", + "labels": ["bug", "fix"], + "rules": [ + { + "pattern": "^fix(\\(.+\\))?:", + "on_property": "title" + } + ] + }, + { + "title": "## ๐Ÿ“š Documentation", + "labels": ["documentation"], + "rules": [ + { + "pattern": "^docs(\\(.+\\))?:", + "on_property": "title" + } + ] + }, + { + "title": "## ๐ŸŽจ Code Style", + "labels": ["style"], + "rules": [ + { + "pattern": "^style(\\(.+\\))?:", + "on_property": "title" + } + ] + }, + { + "title": "## โ™ป๏ธ Refactoring", + "labels": ["refactor"], + "rules": [ + { + "pattern": "^refactor(\\(.+\\))?:", + "on_property": "title" + } + ] + }, + { + "title": "## โšก Performance", + "labels": ["performance"], + "rules": [ + { + "pattern": "^perf(\\(.+\\))?:", + "on_property": "title" + } + ] + }, + { + "title": "## โœ… Tests", + "labels": ["test"], + "rules": [ + { + "pattern": "^test(\\(.+\\))?:", + "on_property": "title" + } + ] + }, + { + "title": "## ๐Ÿ”ง Build & CI", + "labels": ["build", "ci"], + "rules": [ + { + "pattern": "^(build|ci)(\\(.+\\))?:", + "on_property": "title" + } + ] }, { "title": "## โคต๏ธ Dependencies", - "labels": ["dependencies"] + "labels": ["dependencies"], + "rules": [ + { + "pattern": "^(chore|deps)(\\(.+\\))?:", + "on_property": "title" + } + ] }, { - "title": "## ๐Ÿ’ญ Uncategorized", + "title": "## ๐Ÿ’ญ Other Changes", "labels": [] } ], "template": "#{{CHANGELOG}}", - "pr_template": "* #{{TITLE}} by @#{{AUTHOR}} in ##{{NUMBER}}" + "pr_template": "* #{{TITLE}} by @#{{AUTHOR}} in ##{{NUMBER}}", + "empty_template": "No changes", + "transformers": [ + { + "pattern": "^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|deps)(\\(.+\\))?:\\s*(.+)", + "target": "$3" + } + ] } diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..bd020cb --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,336 @@ +name: Build and Publish Docker Images + +on: + push: + tags: + - 'v*.*.*' + +env: + REGISTRY: ghcr.io + REGISTRY_IMAGE: ${{ github.repository_owner }}/fancy-gatus + +permissions: + contents: write + packages: write + attestations: write + id-token: write + +jobs: + build_and_test: + name: Build and Test + runs-on: ${{ matrix.architecture == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }} + strategy: + fail-fast: false + matrix: + architecture: + - amd64 + - arm64 + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build application + run: npm run build + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log into GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Get Docker Metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.REGISTRY_IMAGE }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha,prefix={{branch}}-,enable={{is_default_branch}} + type=sha + type=semver,pattern={{version}} + type=semver,pattern={{major}} + type=raw,value=latest + + - name: Build Docker Container + id: push + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + platforms: linux/${{ matrix.architecture }} + labels: ${{ steps.meta.outputs.labels }} + tags: ${{ steps.meta.outputs.tags }} + push: true + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Generate Artifact Attestation + uses: actions/attest-build-provenance@v2 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.REGISTRY_IMAGE }} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true + + - name: Export digest + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.push.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-linux-${{ matrix.architecture }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + + merge_platform_containers: + name: Merge Platform Containers + runs-on: ubuntu-24.04 + needs: build_and_test + steps: + - name: Download Digests + uses: actions/download-artifact@v4 + with: + path: ${{ runner.temp }}/digests + pattern: digests-* + merge-multiple: true + + - name: Log into GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Get Docker Metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.REGISTRY_IMAGE }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha,prefix={{branch}}-,enable={{is_default_branch}} + type=sha + type=semver,pattern={{version}} + type=semver,pattern={{major}} + type=raw,value=latest + + - name: Create manifest list and push + working-directory: ${{ runner.temp }}/digests + run: | + # Debug: List all downloaded files + echo "Downloaded digest files:" + find . -type f -name "*" | sort + + # Create manifest list with proper platform annotations + TAGS=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") + + # Build sources from all digest files + SOURCES="" + for digest_file in *; do + if [ -f "$digest_file" ]; then + digest="sha256:${digest_file}" + SOURCES="${SOURCES} ${{ env.REGISTRY }}/${{ env.REGISTRY_IMAGE }}@${digest}" + fi + done + + # Create manifest list + echo "Creating manifest with sources: ${SOURCES}" + if [ -n "$SOURCES" ]; then + docker buildx imagetools create ${TAGS} ${SOURCES} + else + echo "ERROR: No digest sources found" + exit 1 + fi + + - name: Inspect Image + run: | + docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} + + create_release: + name: Create GitHub Release + runs-on: ubuntu-24.04 + needs: merge_platform_containers + if: startsWith(github.ref, 'refs/tags/v') + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies and build + run: | + npm ci + npm run build + + - name: Create output zip + run: cd build; zip -r ../fancy-gatus-${{ github.ref_name }}.zip * + + - name: Build changelog + id: changelog + uses: mikepenz/release-changelog-builder-action@v5 + with: + configuration: '.github/changelog-configuration.json' + failOnError: false + mode: "HYBRID" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate commit-based changelog if no PRs found + id: commit_changelog + if: steps.changelog.outputs.changelog == '' || contains(steps.changelog.outputs.changelog, 'No pull requests found') + run: | + echo "changelog<> $GITHUB_OUTPUT + + # Get commits between tags + commits=$(git log --pretty=format:"%s (%h)" ${{ github.event.before }}..${{ github.sha }}) + + # Categorize commits based on conventional commit prefixes + features=$(echo "$commits" | grep -E "^feat(\(.+\))?: " || true) + fixes=$(echo "$commits" | grep -E "^fix(\(.+\))?: " || true) + docs=$(echo "$commits" | grep -E "^docs(\(.+\))?: " || true) + style=$(echo "$commits" | grep -E "^style(\(.+\))?: " || true) + refactor=$(echo "$commits" | grep -E "^refactor(\(.+\))?: " || true) + perf=$(echo "$commits" | grep -E "^perf(\(.+\))?: " || true) + test=$(echo "$commits" | grep -E "^test(\(.+\))?: " || true) + build=$(echo "$commits" | grep -E "^(build|ci)(\(.+\))?: " || true) + chore=$(echo "$commits" | grep -E "^(chore|deps)(\(.+\))?: " || true) + other=$(echo "$commits" | grep -vE "^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|deps)(\(.+\))?: " || true) + + # Generate categorized changelog + if [ -n "$features" ]; then + echo "## ๐Ÿš€ Features" >> $GITHUB_OUTPUT + echo "$features" | sed 's/^feat[^:]*: /- /' | sed 's/ (/ (/' >> $GITHUB_OUTPUT + echo "" >> $GITHUB_OUTPUT + fi + + if [ -n "$fixes" ]; then + echo "## ๐Ÿ› Bug Fixes" >> $GITHUB_OUTPUT + echo "$fixes" | sed 's/^fix[^:]*: /- /' | sed 's/ (/ (/' >> $GITHUB_OUTPUT + echo "" >> $GITHUB_OUTPUT + fi + + if [ -n "$docs" ]; then + echo "## ๐Ÿ“š Documentation" >> $GITHUB_OUTPUT + echo "$docs" | sed 's/^docs[^:]*: /- /' | sed 's/ (/ (/' >> $GITHUB_OUTPUT + echo "" >> $GITHUB_OUTPUT + fi + + if [ -n "$style" ]; then + echo "## ๐ŸŽจ Code Style" >> $GITHUB_OUTPUT + echo "$style" | sed 's/^style[^:]*: /- /' | sed 's/ (/ (/' >> $GITHUB_OUTPUT + echo "" >> $GITHUB_OUTPUT + fi + + if [ -n "$refactor" ]; then + echo "## โ™ป๏ธ Refactoring" >> $GITHUB_OUTPUT + echo "$refactor" | sed 's/^refactor[^:]*: /- /' | sed 's/ (/ (/' >> $GITHUB_OUTPUT + echo "" >> $GITHUB_OUTPUT + fi + + if [ -n "$perf" ]; then + echo "## โšก Performance" >> $GITHUB_OUTPUT + echo "$perf" | sed 's/^perf[^:]*: /- /' | sed 's/ (/ (/' >> $GITHUB_OUTPUT + echo "" >> $GITHUB_OUTPUT + fi + + if [ -n "$test" ]; then + echo "## โœ… Tests" >> $GITHUB_OUTPUT + echo "$test" | sed 's/^test[^:]*: /- /' | sed 's/ (/ (/' >> $GITHUB_OUTPUT + echo "" >> $GITHUB_OUTPUT + fi + + if [ -n "$build" ]; then + echo "## ๐Ÿ”ง Build & CI" >> $GITHUB_OUTPUT + echo "$build" | sed 's/^(build|ci)[^:]*: /- /' | sed 's/ (/ (/' >> $GITHUB_OUTPUT + echo "" >> $GITHUB_OUTPUT + fi + + if [ -n "$chore" ]; then + echo "## โคต๏ธ Dependencies" >> $GITHUB_OUTPUT + echo "$chore" | sed 's/^(chore|deps)[^:]*: /- /' | sed 's/ (/ (/' >> $GITHUB_OUTPUT + echo "" >> $GITHUB_OUTPUT + fi + + if [ -n "$other" ]; then + echo "## ๐Ÿ’ญ Other Changes" >> $GITHUB_OUTPUT + echo "$other" | sed 's/^/- /' >> $GITHUB_OUTPUT + echo "" >> $GITHUB_OUTPUT + fi + + echo "EOF" >> $GITHUB_OUTPUT + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + name: Fancy Gatus ${{ github.ref_name }} + body: | + ${{ steps.commit_changelog.outputs.changelog || steps.changelog.outputs.changelog }} + + ## ๐Ÿณ Docker Images + + Multi-platform Docker images are available at: + ```bash + docker pull ${{ env.REGISTRY }}/${{ env.REGISTRY_IMAGE }}:${{ github.ref_name }} + docker pull ${{ env.REGISTRY }}/${{ env.REGISTRY_IMAGE }}:latest + ``` + + ## ๐Ÿš€ Quick Start + + ### Docker Run + ```bash + docker run -d -p 3000:80 \ + -e CONFIG_TITLE="My Status Page" \ + -e CONFIG_HIDE_URLS="true" \ + -e CONFIG_HIDE_FOOTER="true" \ + ${{ env.REGISTRY }}/${{ env.REGISTRY_IMAGE }}:${{ github.ref_name }} + ``` + + ### Docker Compose + ```yaml + version: '3.8' + services: + fancy-gatus: + image: ${{ env.REGISTRY }}/${{ env.REGISTRY_IMAGE }}:${{ github.ref_name }} + ports: + - "3000:80" + environment: + CONFIG_TITLE: "My Infrastructure Status" + CONFIG_GATUS_BASE_URL: "https://status.example.com" + CONFIG_HIDE_URLS: "false" + CONFIG_HIDE_FOOTER: "false" + ``` + + ## ๐Ÿ“ฆ Static Build + + Download the static build zip file from the assets below and serve with any web server. + + ## ๐Ÿ—๏ธ Supported Platforms + - linux/amd64 + - linux/arm64 + files: fancy-gatus-${{ github.ref_name }}.zip + fail_on_unmatched_files: true \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index e9e7ee3..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Publish release -on: - push: - tags: - - 'v*.*.*' - -permissions: - contents: write - -jobs: - release: - name: Release pushed tag - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 22.x - - name: Clean install dependencies and build - run: | - npm ci - npm run build - - name: Create output zip - run: cd build; zip -r ../fancy-gatus-${{ github.ref_name }}.zip * - - name: Build changelog - id: release - uses: mikepenz/release-changelog-builder-action@v5 - with: - configuration: '.github/changelog-configuration.json' - failOnError: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Publish release - uses: softprops/action-gh-release@v2 - with: - name: Fancy Gatus ${{ github.ref_name }} - body: ${{ steps.release.outputs.changelog }} - files: fancy-gatus-${{ github.ref_name }}.zip - fail_on_unmatched_files: true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..28bf779 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +# Build stage +FROM node:18-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci + +# Copy source code +COPY . . + +# Build the application +RUN npm run build + +# Production stage using nginx:alpine +FROM nginx:alpine + +# Copy built application to nginx webroot +COPY --from=builder /app/build /usr/share/nginx/html + +# Copy configuration and auth setup scripts +COPY scripts/generate-config.sh /usr/local/bin/generate-config.sh +COPY scripts/setup-nginx.sh /usr/local/bin/setup-nginx.sh + +# Make scripts executable +RUN chmod +x /usr/local/bin/generate-config.sh /usr/local/bin/setup-nginx.sh + +# Expose configurable port (default 80 for nginx:alpine) +ARG NGINX_PORT=80 +ENV NGINX_PORT=${NGINX_PORT} +EXPOSE ${NGINX_PORT} + +# Setup nginx config, generate frontend config, and start nginx +CMD ["/bin/sh", "-c", "/usr/local/bin/setup-nginx.sh && /usr/local/bin/generate-config.sh && nginx -g 'daemon off;'"] \ No newline at end of file diff --git a/README.md b/README.md index 37cfc65..ab586d7 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,63 @@ You can see it in action [here](https://status.bluemedia.dev) or check out the s ## Deployment -Fancy Gatus is intended to be delivered directly from a web server (e.g. Nginx). The installation is therefore simple. +### Static Files + +Fancy Gatus is intended to be delivered directly from a web server (e.g. Nginx). The installation is therefore simple. Just download the latest ZIP file from the release page and unpack it into the web root of your server. If you want to make further adjustments to the frontend, you can also create the configuration file in the web root. Refer to the [Configuration](#configuration) section for more information. Make sure that the Gatus API endpoint `/api/v1/endpoints/statuses` is available relative to the frontend if you have not configured a different base URL. An example configuration for Nginx that makes this possible can be found [here](docs/example-nginx.conf). +### Docker + +Fancy Gatus can also be deployed using Docker with the included Dockerfile that uses the lightweight and reliable [nginx:alpine](https://hub.docker.com/_/nginx) base image. + +#### Quick Start + +```bash +# Using docker-compose +docker-compose up -d + +# Or build and run manually +docker build -t fancy-gatus . +docker run -d -p 3000:80 \ + -e CONFIG_TITLE="My Status Page" \ + -e CONFIG_HIDE_URLS="true" \ + fancy-gatus +``` + +#### Environment Variables + +Configuration is handled through environment variables at runtime: + +| Variable | Description | Default | +|----------|-------------|---------| +| `CONFIG_TITLE` | Page title | `Infrastructure Status` | +| `CONFIG_GATUS_BASE_URL` | Base URL of Gatus API | _(none)_ | +| `CONFIG_HIDE_URLS` | Hide service URLs | `false` | +| `CONFIG_HIDE_FOOTER` | Hide the "Powered by Gatus and Fancy Gatus" footer | `false` | +| `CONFIG_HIDDEN_GROUPS` | Comma-separated list of hidden groups | _(none)_ | +| `CONFIG_HIDDEN_STATUSES` | Comma-separated list of hidden statuses | _(none)_ | +| `CONFIG_GROUP_ORDER` | Comma-separated list for group ordering | _(none)_ | +| `CONFIG_DEFAULT_EXPAND_GROUPS` | Expand groups by default | `false` | +| `CONFIG_DEFAULT_REFRESH_INTERVAL` | Refresh interval in seconds | `60` | +| `CONFIG_NOTICE_TYPE` | Notice type (`success`, `warning`, `error`) | _(none)_ | +| `CONFIG_NOTICE_TITLE` | Notice title | _(none)_ | +| `CONFIG_NOTICE_CONTENT` | Notice content | _(none)_ | +| `CONFIG_NOTICE_CREATED_AT` | Notice creation date | _(none)_ | +| `CONFIG_NOTICE_UPDATED_AT` | Notice update date | _(none)_ | +| `GATUS_API_URL` | Proxy API requests to this URL | _(none)_ | +| `NGINX_PORT` | Port for nginx to listen on | `80` | + +#### Security Features + +The Docker image includes several security features: +- Lightweight nginx:alpine base image +- Read-only filesystem +- Automatic security scanning +- Minimal attack surface +- Efficient Alpine Linux base + ## Configuration The frontend tries to retrieve a configuration file named `config.json` from the webroot during page load. If the configuration is loaded successfully, it will be used to adjust the frontend. The possible options are listed below. @@ -28,6 +80,8 @@ The frontend tries to retrieve a configuration file named `config.json` from the | :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------- | | `title` | Title of the page. Both in the tab and next to the logo. | `Infrastructure Status` | | `gatusBaseUrl` | Alternative base URL of the Gatus instance, if the API is not available relative to the frontend. | `none` | +| `hideUrls` | Boolean specifying if service URLs/hostnames should be hidden from the status display for privacy or security reasons. | `false` | +| `hideFooter` | Boolean specifying if the "Powered by Gatus and Fancy Gatus" footer should be hidden from the page. | `false` | | `hiddenGroups` | Array containing names of groups that should be hidden. These groups are still visible in the API response! | `[]` | | `hiddenStatuses` | Array containing names of statuses that should be hidden. These are still visible in the API response! | `[]` | | `groupOrder` | Array containing names of groups. The groups are sorted in the frontend according to the order in the array (different from alphabetical sorting by default). If groups are not included in the array, they will be added alphabetically sorted below the sorted groups. | `[]` | diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..cefe807 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,46 @@ +version: '3.8' + +services: + fancy-gatus: + build: . + container_name: fancy-gatus + ports: + - "3000:80" + environment: + # Basic configuration + CONFIG_TITLE: "My Infrastructure Status" + CONFIG_GATUS_BASE_URL: "https://status.example.com" + CONFIG_HIDE_URLS: "false" + CONFIG_HIDE_FOOTER: "false" + + # Groups and statuses configuration + CONFIG_HIDDEN_GROUPS: "" + CONFIG_HIDDEN_STATUSES: "" + CONFIG_GROUP_ORDER: "" + CONFIG_DEFAULT_EXPAND_GROUPS: "false" + CONFIG_DEFAULT_REFRESH_INTERVAL: "60" + + # Notice configuration (optional) + CONFIG_NOTICE_TYPE: "" + CONFIG_NOTICE_TITLE: "" + CONFIG_NOTICE_CONTENT: "" + CONFIG_NOTICE_CREATED_AT: "" + CONFIG_NOTICE_UPDATED_AT: "" + + # API proxy (optional) + GATUS_API_URL: "" + + # Nginx port + NGINX_PORT: "80" + + # Timezone + TZ: "UTC" + + # Health check + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + restart: unless-stopped \ No newline at end of file diff --git a/scripts/generate-config.sh b/scripts/generate-config.sh new file mode 100644 index 0000000..8143a09 --- /dev/null +++ b/scripts/generate-config.sh @@ -0,0 +1,116 @@ +#!/bin/sh + +# Generate config.json from environment variables +# This script runs at container startup to create the frontend configuration + +CONFIG_FILE="/usr/share/nginx/html/config.json" +CONFIG_DIR=$(dirname "$CONFIG_FILE") + +# Ensure the directory exists +mkdir -p "$CONFIG_DIR" + +# Function to convert comma-separated string to JSON array +to_json_array() { + if [ -z "$1" ]; then + echo "[]" + else + echo "$1" | sed 's/,/","/g' | sed 's/^/["/' | sed 's/$/"]/' + fi +} + +# Function to convert boolean string to JSON boolean +to_json_boolean() { + case "$1" in + true|TRUE|True|1|yes|YES|Yes) + echo "true" + ;; + *) + echo "false" + ;; + esac +} + +# Start building the JSON configuration +cat > "$CONFIG_FILE" << EOF +{ +EOF + +# Add title (default: "Infrastructure Status") +TITLE="${CONFIG_TITLE:-Infrastructure Status}" +echo " \"title\": \"$TITLE\"," >> "$CONFIG_FILE" + +# Add gatusBaseUrl if provided +if [ -n "$CONFIG_GATUS_BASE_URL" ]; then + echo " \"gatusBaseUrl\": \"$CONFIG_GATUS_BASE_URL\"," >> "$CONFIG_FILE" +fi + +# Add hideUrls boolean (default: false) +HIDE_URLS=$(to_json_boolean "$CONFIG_HIDE_URLS") +echo " \"hideUrls\": $HIDE_URLS," >> "$CONFIG_FILE" + +# Add hideFooter boolean (default: false) +HIDE_FOOTER=$(to_json_boolean "$CONFIG_HIDE_FOOTER") +echo " \"hideFooter\": $HIDE_FOOTER," >> "$CONFIG_FILE" + +# Add hiddenGroups array +HIDDEN_GROUPS=$(to_json_array "$CONFIG_HIDDEN_GROUPS") +echo " \"hiddenGroups\": $HIDDEN_GROUPS," >> "$CONFIG_FILE" + +# Add hiddenStatuses array +HIDDEN_STATUSES=$(to_json_array "$CONFIG_HIDDEN_STATUSES") +echo " \"hiddenStatuses\": $HIDDEN_STATUSES," >> "$CONFIG_FILE" + +# Add groupOrder array +GROUP_ORDER=$(to_json_array "$CONFIG_GROUP_ORDER") +echo " \"groupOrder\": $GROUP_ORDER," >> "$CONFIG_FILE" + +# Add defaultExpandGroups boolean (default: false) +DEFAULT_EXPAND_GROUPS=$(to_json_boolean "$CONFIG_DEFAULT_EXPAND_GROUPS") +echo " \"defaultExpandGroups\": $DEFAULT_EXPAND_GROUPS," >> "$CONFIG_FILE" + +# Add defaultRefreshInterval (default: 60) +DEFAULT_REFRESH_INTERVAL="${CONFIG_DEFAULT_REFRESH_INTERVAL:-60}" +echo " \"defaultRefreshInterval\": $DEFAULT_REFRESH_INTERVAL," >> "$CONFIG_FILE" + +# Add notice configuration if any notice variables are set +if [ -n "$CONFIG_NOTICE_TYPE" ] || [ -n "$CONFIG_NOTICE_TITLE" ] || [ -n "$CONFIG_NOTICE_CONTENT" ] || [ -n "$CONFIG_NOTICE_CREATED_AT" ] || [ -n "$CONFIG_NOTICE_UPDATED_AT" ]; then + echo " \"notice\": {" >> "$CONFIG_FILE" + + # Add notice type if provided + if [ -n "$CONFIG_NOTICE_TYPE" ]; then + echo " \"type\": \"$CONFIG_NOTICE_TYPE\"," >> "$CONFIG_FILE" + fi + + # Add notice title (default: empty string) + NOTICE_TITLE="${CONFIG_NOTICE_TITLE:-}" + echo " \"title\": \"$NOTICE_TITLE\"," >> "$CONFIG_FILE" + + # Add notice content (default: empty string) + NOTICE_CONTENT="${CONFIG_NOTICE_CONTENT:-}" + echo " \"content\": \"$NOTICE_CONTENT\"," >> "$CONFIG_FILE" + + # Add notice createdAt if provided + if [ -n "$CONFIG_NOTICE_CREATED_AT" ]; then + echo " \"createdAt\": \"$CONFIG_NOTICE_CREATED_AT\"," >> "$CONFIG_FILE" + fi + + # Add notice updatedAt if provided (remove trailing comma if this is the last item) + if [ -n "$CONFIG_NOTICE_UPDATED_AT" ]; then + echo " \"updatedAt\": \"$CONFIG_NOTICE_UPDATED_AT\"" >> "$CONFIG_FILE" + else + # Remove the trailing comma from the last added line + sed -i '$ s/,$//' "$CONFIG_FILE" + fi + + echo " }" >> "$CONFIG_FILE" +else + # Remove the trailing comma from defaultRefreshInterval if no notice section + sed -i '$ s/,$//' "$CONFIG_FILE" +fi + +# Close the JSON object +echo "}" >> "$CONFIG_FILE" + +echo "Configuration file generated at $CONFIG_FILE" +echo "Configuration contents:" +cat "$CONFIG_FILE" \ No newline at end of file diff --git a/scripts/setup-nginx.sh b/scripts/setup-nginx.sh new file mode 100644 index 0000000..f5588da --- /dev/null +++ b/scripts/setup-nginx.sh @@ -0,0 +1,94 @@ +#!/bin/sh + +# Setup nginx configuration for fancy-gatus +# This script runs at container startup to configure nginx + +NGINX_CONF="/etc/nginx/conf.d/default.conf" +NGINX_CONF_DIR=$(dirname "$NGINX_CONF") + +# Ensure the nginx config directory exists +mkdir -p "$NGINX_CONF_DIR" + +# Set the port (default to 80 for nginx:alpine) +NGINX_PORT="${NGINX_PORT:-80}" + +echo "Setting up nginx configuration on port $NGINX_PORT..." + +# Create nginx configuration for fancy-gatus +cat > "$NGINX_CONF" << EOF +server { + listen $NGINX_PORT; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + # Handle SPA routing - serve index.html for all routes + location / { + try_files \$uri \$uri/ /index.html; + } + + # Handle static assets with caching + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files \$uri =404; + } + + # Handle config.json with no caching (so env changes are picked up) + location = /config.json { + expires -1; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + try_files \$uri =404; + } + + # Handle API proxy if GATUS_API_URL is provided + location /api/ { +EOF + +# Add API proxy configuration if GATUS_API_URL is provided +if [ -n "$GATUS_API_URL" ]; then + cat >> "$NGINX_CONF" << EOF + proxy_pass ${GATUS_API_URL}; + 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; + + # CORS headers for API requests + add_header Access-Control-Allow-Origin "*" always; + add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always; + add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization" always; + + if (\$request_method = 'OPTIONS') { + return 204; + } +EOF +else + cat >> "$NGINX_CONF" << EOF + return 404; +EOF +fi + +cat >> "$NGINX_CONF" << EOF + } + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # Hide nginx version + server_tokens off; + + # Access logs + access_log /var/log/nginx/access.log; + error_log /dev/stderr warn; +} +EOF + +echo "Nginx configuration created at $NGINX_CONF" +echo "Configuration contents:" +cat "$NGINX_CONF" \ No newline at end of file diff --git a/src/lib/components/ErrorDisplay.svelte b/src/lib/components/ErrorDisplay.svelte new file mode 100644 index 0000000..fd00427 --- /dev/null +++ b/src/lib/components/ErrorDisplay.svelte @@ -0,0 +1,109 @@ + + +
+
+
+ {getErrorIcon(props.error)} +

Connection Failed

+
+ +

+ {getErrorMessage(props.error)} +

+ +
+

Troubleshooting:

+
    +
  • Verify the Gatus base URL in your configuration
  • +
  • Ensure the Gatus API endpoint /api/v1/endpoints/statuses is accessible
  • +
  • Check if Gatus is running and healthy
  • +
  • Verify network connectivity and firewall settings
  • +
+
+ + {#if props.retry} +
+ +
+ {/if} +
+
+ + \ No newline at end of file diff --git a/src/lib/components/Status.svelte b/src/lib/components/Status.svelte index 6f50542..a194546 100644 --- a/src/lib/components/Status.svelte +++ b/src/lib/components/Status.svelte @@ -1,8 +1,10 @@